Rocky 10: a dnsmasq forwarder that would not cache
Test scope
Section titled “Test scope”A Rocky Linux 10.2 Server VM with dnsmasq-2.90-7.el10_2.
This host is authoritative for nothing; it forwards to the LAN resolver (192.168.2.241) and caches the answers. Screenshot: public/evidence/dnsmasq/rocky-forwarder.png.
Configuration
Section titled “Configuration”listen-address=192.168.2.243bind-interfacesserver=192.168.2.241no-resolvcache-size=1000log-queriesserver=: the only upstream;no-resolv: ignore/etc/resolv.confso the system cannot override it;cache-size=1000: cache entry limit;log-queries: writes every query and cache hit to the journal — the switch that makes this debuggable.
Reading cache hits from the journal
Section titled “Reading cache hits from the journal”$ dig @192.168.2.243 c1.lab.test A +short$ dig @192.168.2.243 c1.lab.test A +short$ journalctl -u dnsmasq --since "-40s" --no-pager | grep -E "c1"... dnsmasq[11792]: query[A] c1.lab.test from 192.168.2.243... dnsmasq[11792]: forwarded c1.lab.test to 192.168.2.241... dnsmasq[11792]: query[A] c1.lab.test from 192.168.2.243... dnsmasq[11792]: cached c1.lab.test is 192.168.2.241Two query[A] lines, the first forwarded, the second cached — that is the reliable proof a cache works. dig’s Query time cannot show it: on a LAN both read 0 msec.
The trap: TTL 0 disables the cache
Section titled “The trap: TTL 0 disables the cache”In the first run both queries were forwarded, with no cached at all. The cause was upstream:
$ dig @192.168.2.241 web.lab.test A +noall +answerweb.lab.test. 0 IN A 192.168.2.241Records generated by address=/lab.test/… carry TTL 0, which downstream caches must not store — so every query goes back to the source.
After adding local-ttl=60 upstream:
$ dig @192.168.2.241 web.lab.test A +noall +answerweb.lab.test. 60 IN A 192.168.2.241$ journalctl -u dnsmasq | grep c1 | tail -2... query[A] c1.lab.test from 192.168.2.243... cached c1.lab.test is 192.168.2.241Conclusion: when a forwarder “does not cache”, check the upstream TTL before touching cache-size.

Verification order
Section titled “Verification order”1) ss -lunp | grep ':53 ' # which address is bound (bind-interfaces = one)2) dig @<host> <name> A +noall +answer # what TTL the upstream really returns3) journalctl -u dnsmasq -n 20 # forwarded / cached / refused sources4) nft list ruleset | grep 53 # is the firewall dropping itWithout log-queries, step 3 shows nothing — the most common time sink here.
Common traps
Section titled “Common traps”- Judging from a single query: cache hits need a second one;
- Testing via 127.0.0.1: queries outside
listen-addressare refused and never even logged; - Upstream TTL 0: as above;
- Writing only one of
server=/no-resolv: the former missing means asking the system resolver, the latter means NetworkManager may override it.
Practice: make cache hits reproducible
Section titled “Practice: make cache hits reproducible”for i in 1 2; do dig @192.168.2.243 d1.lab.test A +short; donesudo journalctl -u dnsmasq --since "-30s" --no-pager | grep -E "d1"sudo systemctl restart dnsmasqdig @192.168.2.243 d1.lab.test A +short # first query after restart is always forwardedChecks:
- At least the second of the two queries is
cached; - After a restart the first query is always
forwarded(the cache is memory only); - Changing the upstream TTL changes caching behaviour directly;
- No
queryline at all means the request never arrived (address or firewall).
常见问题
What cache-size should I use?
A few thousand entries is plenty for small networks; the limiting factor is TTL, not capacity.
Can different domains use different upstreams?
Yes, dnsmasq 2.86+ supports per-domain forwarding: server=/example.com/1.1.1.1.
Why is it slow right after a restart?
The cache is in memory, so the first query always goes upstream — that is expected.
Next steps
Section titled “Next steps”- Resolver comparison: /en/systemd-resolved/
- Resolver and split DNS: /en/systemd-resolved/
- Troubleshooting: /en/network-troubleshoot/