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.