dnsmasq is a lightweight DNS forwarder and optional DHCP server that runs locally on a machine. Its primary job: accept DNS queries from processes on the machine, forward them to upstream DNS servers, and cache the results.
Why run a local DNS forwarder at all?
When a process calls getaddrinfo("example.com"), the C library reads /etc/resolv.conf to find which DNS server to ask. Without dnsmasq, that file points directly at an upstream server (e.g. 1.1.1.1). Every DNS query goes over the network.
With dnsmasq running locally, /etc/resolv.conf contains nameserver 127.0.0.1 — all queries go to dnsmasq first. dnsmasq either answers from its cache (fast, no network) or forwards to an upstream server.
Benefits:
- Caching — repeated lookups (e.g. the same CDN hostname 50 times a second) are answered locally in microseconds instead of ~20 ms round trips
- Split DNS — send queries for
.corp.internalto an internal server, everything else to1.1.1.1; a single/etc/resolv.confentry cannot do this - VPN DNS — when a VPN adds a private DNS server, dnsmasq can route queries for the VPN domain there without affecting other domains
- Stable resolver — tools like libvirt, k3s, and Docker rely on a stable local resolver for their own DNS zones
How dnsmasq finds upstream servers
dnsmasq reads a file — typically /etc/dnsmasq-resolv.conf or /etc/resolv.conf — to discover which upstream servers to forward queries to. This is separate from the file that tells the OS to use dnsmasq (/etc/resolv.conf pointing to 127.0.0.1).
process calls getaddrinfo("example.com")
→ C library reads /etc/resolv.conf → nameserver 127.0.0.1
→ query goes to dnsmasq on port 53
→ dnsmasq: cache miss → reads /etc/dnsmasq-resolv.conf → forwards to 1.1.1.1
→ caches answer, returns to process
The NM gap problem
NetworkManager manages the current network connection and knows what DNS servers the DHCP server provided. By default NM writes those servers into dnsmasq’s upstream file on every DHCP renewal or network event. This creates a race:
- NM clears the file
- dnsmasq notices, logs
no servers found in /etc/dnsmasq-resolv.conf, will retry - All non-cached DNS queries fail for ~5 seconds
- NM rewrites the file with the new servers
Fix: tell NM to stop managing DNS entirely, and give dnsmasq static upstream servers so it never depends on NM’s file:
# NixOS
networking.networkmanager.dns = "none";
services.dnsmasq.settings = {
no-resolv = true;
server = [ "1.1.1.1" "8.8.8.8" ];
};See NetworkManager for the full diagnosis and fix.