Installing Dnsmasq in My Homelab

Dnsmasq is an application which has features like DNS caching, DHCP server, and so on. It’s useful as a DNS server in a homelab as it can reply to DNS queries for what’s in its database, and forward everything else to an authoritative server or recursive resolver. This is useful in my lab as I want to resolve for example gitlab.lostintransit.se locally, but lostintransit.se (my web server) or any other domains via recursive resolvers. Most DNS servers are authoritative for a zone or parts of a zone, but dnsmasq responds only to what it knows and forwards everything else, which is perfect for my needs.

First I’ll configure a static IP on the host by modifying the file in /etc/netplan:

sudo vi 50-cloud-init.yaml

The contents are now:

sudo cat 50-cloud-init.yaml 
network:
  version: 2
  ethernets:
    ens160:
      dhcp4: no
      addresses:
        - 192.168.128.53/24
      routes:
        - to: default
          via: 192.168.128.1
      nameservers:
        addresses:
          - 8.8.8.8
          - 8.8.4.4

I’m using recursive resolvers for now but will update it later.

Then apply the configuration:

sudo netplan apply

The IP has been updated:

ip addr show ens160
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:ad:4c:f1 brd ff:ff:ff:ff:ff:ff
    altname enp3s0
    inet 192.168.128.53/24 brd 192.168.128.255 scope global ens160
       valid_lft forever preferred_lft forever
    inet6 fe80::250:56ff:fead:4cf1/64 scope link 
       valid_lft forever preferred_lft forever

Now we install dnsmasq:

sudo apt install -y dnsmasq

Check the version that has been installed:

dnsmasq --version
Dnsmasq version 2.90  Copyright (c) 2000-2024 Simon Kelley
Compile time options: IPv6 GNU-getopt DBus no-UBus i18n IDN2 DHCP DHCPv6 no-Lua TFTP conntrack ipset nftset auth cryptohash DNSSEC loop-detect inotify dumpfile

This software comes with ABSOLUTELY NO WARRANTY.
Dnsmasq is free software, and you are welcome to redistribute it
under the terms of the GNU General Public License, version 2 or 3.

Then we check if systemd-resolved is running:

systemctl is-active systemd-resolved
active

We can also confirm that we are already running a service on port 53:

sudo ss -tulpn | grep :53
udp   UNCONN 0      0         127.0.0.54:53        0.0.0.0:*    users:(("systemd-resolve",pid=632,fd=16))              
udp   UNCONN 0      0      127.0.0.53%lo:53        0.0.0.0:*    users:(("systemd-resolve",pid=632,fd=14))              
tcp   LISTEN 0      4096      127.0.0.54:53        0.0.0.0:*    users:(("systemd-resolve",pid=632,fd=17))              
tcp   LISTEN 0      4096   127.0.0.53%lo:53        0.0.0.0:*    users:(("systemd-resolve",pid=632,fd=15))  

Next we stop the systemd-resolved service:

sudo systemctl stop systemd-resolved

We also need to disable it from starting at bootup:

sudo systemctl disable systemd-resolved
Removed "/etc/systemd/system/dbus-org.freedesktop.resolve1.service".
Removed "/etc/systemd/system/sysinit.target.wants/systemd-resolved.service".

We also remove the symlink to /etc/resolv.conf:

sudo unlink /etc/resolv.conf

Then we create the file, which is now using static configuration:

echo -e "nameserver 8.8.8.8\nnameserver 8.8.4.4" | sudo tee /etc/resolv.conf
nameserver 8.8.8.8
nameserver 8.8.4.4

We verify the contents of the file:

sudo cat /etc/resolv.conf 
nameserver 8.8.8.8
nameserver 8.8.4.4

Verify that name resolution is still working:

ping google.com
PING google.com (216.58.207.110) 56(84) bytes of data.
64 bytes from lcarna-ag-in-f14.1e100.net (216.58.207.110): icmp_seq=1 ttl=117 time=7.14 ms
64 bytes from lcarna-ag-in-f14.1e100.net (216.58.207.110): icmp_seq=2 ttl=117 time=7.44 ms

--- google.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 7.144/7.291/7.438/0.147 ms

Now we need to provide configuration for dnsmasq in the file /etc/dnsmasq.conf. Because the existing configuration file is quite bloated, I’ll just rename it and create a new file:

sudo mv dnsmasq.conf dnsmasq.conf.bak
sudo touch dnsmasq.conf
sudo vi dnsmasq.conf

The file now looks like this:

sudo cat dnsmasq.conf
# ── Interface & port ──────────────────────────────────────────────────
listen-address=127.0.0.1,192.168.128.53
bind-interfaces
port=53

# ── DNS cache ─────────────────────────────────────────────────────────
cache-size=1000
neg-ttl=60

# ── Upstream resolvers ────────────────────────────────────────────────
no-resolv
server=8.8.8.8
server=8.8.4.4

# ── Logging ───────────────────────────────────────────────────────────
log-queries
log-facility=/var/log/dnsmasq.log

# ── Additional config files ───────────────────────────────────────────
# Load all .conf files from /etc/dnsmasq.d/ (e.g. lab.conf)
conf-dir=/etc/dnsmasq.d/,*.conf

First we configure the server to listen on 127.0.0.1 and 192.168.128.53 on port 53. We create a dedicated socket per interface using bind-interfaces. We cache up to 1000 entries and we save negative replies (NXDOMAIN) for 60 seconds. The recursive resolvers are 8.8.8.8 and 8.8.4.4. We use no-resolv to not get the recursive resolvers from /etc/resolv.conf. We also log all queries to /var/log/dnsmasq.log. We read configuration from all .conf files in /etc/dnsmasq.d.

Next well create a file named lab.conf in /etc/dnsmasq.d:

sudo touch lab.conf

The file has the following contents:

sudo cat lab.conf 
# ── Local A records ───────────────────────────────────────────────────
address=/gitlab.lostintransit.se/192.168.128.10
address=/stepca.lostintransit.se/192.168.128.20
address=/dnsmasq.lostintransit.se/192.168.128.53

# ── Reverse DNS ────────────────────────────────────────────

ptr-record=10.128.168.192.in-addr.arpa,gitlab.lostintransit.se
ptr-record=20.128.168.192.in-addr.arpa,stepca.lostintransit.se
ptr-record=53.128.168.192.in-addr.arpa,dnsmasq.lostintransit.se

Next we enable the service:

sudo systemctl enable dnsmasq
sudo systemctl restart dnsmasq
Synchronizing state of dnsmasq.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install enable dnsmasq

Then check the status:

sudo systemctl status dnsmasq
● dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
     Loaded: loaded (/usr/lib/systemd/system/dnsmasq.service; enabled; preset: enabled)
     Active: active (running) since Wed 2026-06-10 12:37:19 UTC; 21s ago
    Process: 6163 ExecStartPre=/usr/share/dnsmasq/systemd-helper checkconfig (code=exited, status=0/SUCCESS)
    Process: 6169 ExecStart=/usr/share/dnsmasq/systemd-helper exec (code=exited, status=0/SUCCESS)
    Process: 6176 ExecStartPost=/usr/share/dnsmasq/systemd-helper start-resolvconf (code=exited, status=0/SUCCESS)
   Main PID: 6174 (dnsmasq)
      Tasks: 1 (limit: 2263)
     Memory: 1020.0K (peak: 2.4M)
        CPU: 56ms
     CGroup: /system.slice/dnsmasq.service
             └─6174 /usr/sbin/dnsmasq -x /run/dnsmasq/dnsmasq.pid -u dnsmasq -r /run/dnsmasq/resolv.conf -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --local-service --trust-anchor=.,20326,8,2,E06D44B80>

Jun 10 12:37:19 dnsmasq systemd[1]: Starting dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server...
Jun 10 12:37:19 dnsmasq resolvconf[6182]: Dropped protocol specifier '.dnsmasq' from 'lo.dnsmasq'. Using 'lo' (ifindex=1).
Jun 10 12:37:19 dnsmasq resolvconf[6182]: Failed to set DNS configuration: Unit dbus-org.freedesktop.resolve1.service not found.
Jun 10 12:37:19 dnsmasq systemd[1]: Started dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server.

There’s a warning related to that we disabled systemd-resolved. We can safely ignore this.

Next we’ll update our FW with rules to allow DNS:

sudo ufw allow 53/tcp
sudo ufw allow 53/udp
sudo ufw enable

Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

The firewall is now active:

sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
53/tcp                     ALLOW       Anywhere                  
53/udp                     ALLOW       Anywhere                  
22/tcp                     ALLOW       Anywhere                  
53/tcp (v6)                ALLOW       Anywhere (v6)             
53/udp (v6)                ALLOW       Anywhere (v6)             
22/tcp (v6)                ALLOW       Anywhere (v6)  

Now we’ll check the log while running a query from another host:

sudo tail -f /var/log/dnsmasq.log
Jun 10 12:37:19 dnsmasq[6174]: started, version 2.90 cachesize 1000
Jun 10 12:37:19 dnsmasq[6174]: compile time options: IPv6 GNU-getopt DBus no-UBus i18n IDN2 DHCP DHCPv6 no-Lua TFTP conntrack ipset nftset auth cryptohash DNSSEC loop-detect inotify dumpfile
Jun 10 12:37:19 dnsmasq[6174]: using nameserver 8.8.8.8#53
Jun 10 12:37:19 dnsmasq[6174]: using nameserver 8.8.4.4#53
Jun 10 12:37:19 dnsmasq[6174]: read /etc/hosts - 8 names
Jun 10 12:54:27 dnsmasq[6174]: exiting on receipt of SIGTERM
Jun 10 12:54:27 dnsmasq[6579]: started, version 2.90 cachesize 1000
Jun 10 12:54:27 dnsmasq[6579]: compile time options: IPv6 GNU-getopt DBus no-UBus i18n IDN2 DHCP DHCPv6 no-Lua TFTP conntrack ipset nftset auth cryptohash DNSSEC loop-detect inotify dumpfile
Jun 10 12:54:27 dnsmasq[6579]: warning: ignoring resolv-file flag because no-resolv is set
Jun 10 12:54:27 dnsmasq[6579]: using nameserver 8.8.8.8#53
Jun 10 12:54:27 dnsmasq[6579]: using nameserver 8.8.4.4#53
Jun 10 12:54:27 dnsmasq[6579]: read /etc/hosts - 8 names
Jun 10 12:54:41 dnsmasq[6579]: query[A] google.com from 192.168.128.53
Jun 10 12:54:41 dnsmasq[6579]: forwarded google.com to 8.8.8.8
Jun 10 12:54:41 dnsmasq[6579]: forwarded google.com to 8.8.4.4
Jun 10 12:54:41 dnsmasq[6579]: reply google.com is 142.251.38.110
Jun 10 12:55:08 dnsmasq[6579]: query[A] gitlab.lostintransit.se from 192.168.128.193
Jun 10 12:55:08 dnsmasq[6579]: config gitlab.lostintransit.se is 192.168.128.10

It’s working!

Finally, we will configure log rotation in /etc/logrotate.d:

sudo touch dnsmasq
sudo vi dnsmasq

The file looks like this:

cat dnsmasq 
/var/log/dnsmasq.log {
    weekly
    size 50M
    rotate 4
    compress
    delaycompress
    missingok
    notifempty
    dateext
    postrotate
        systemctl kill -s HUP dnsmasq
    endscript
}

We will rotate the log every week, or if the file grows to 50 MB before that. We will keep the four latest files and delete older files. The logs will be compressed, but not the most recently rotated file. We don’t throw an error if the log file is missing and we don’t rotate if the log file is empty. We add the date to the logfile. We run a command when rotating the log, to get dnsmasq to reopen its log file.

That’s it! We now have a lightweight DNS server running locally and we forward everything we haven’t configured locally to the recursive resolvers.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top