dig +trace, and how to tell whose DNS is actually lying to you
Published by RodHat

Somebody changed a record forty minutes ago. Half the estate sees the new value, half sees the old one, and everyone has agreed to call it “propagation” — a word that describes nothing and explains less. DNS does not propagate. Records don’t travel. Resolvers cache answers for the TTL and then ask again, and every confusing DNS situation is a question about which cache you happened to hit.
Three commands separate the possibilities.
Ask the authority, skipping every cache
dig +trace example.com A
This does what a resolver does, out loud: query a root server, follow the referral to the TLD servers, follow that referral to the zone’s nameservers, and ask the last one directly. Every step is printed, and nothing is cached along the way.
If +trace returns the new value, the zone is correct and you are looking at a caching problem downstream. If +trace returns the old value, the change never landed — the record wasn’t saved, it was saved in a different zone, or there are two zones for the same name and you edited the one that isn’t delegated. That last one is more common than it has any right to be.
Read the referrals as they go past. The NS records at each level tell you which servers the parent zone says are authoritative. If those don’t match what you think your nameservers are, you’ve found something better than a DNS bug — you’ve found a delegation that points at a provider somebody stopped paying in 2023.
Ask one specific resolver, without letting it cheat
dig @10.0.0.53 example.com A +norecurse
+norecurse clears the RD (recursion desired) bit. The resolver will answer only from cache — it is not allowed to go ask upstream. So:
- You get an answer → that resolver has it cached, and the
ttlfield tells you exactly how many seconds until it expires. That’s your remaining wait, precisely, not a guess. - You get an empty answer → not cached. The next real query will fetch the current value.
This is the command that turns “how long until it propagates” from folklore into a number. Run it against each resolver your clients use and you will find the one holding the stale record, with its countdown printed next to it.
Check what the client is actually doing
dig example.com A # uses /etc/resolv.conf
resolvectl query example.com # systemd-resolved's view, which may differ
getent hosts example.com # goes through NSS — /etc/hosts, mDNS, everything
These three disagreeing is the most under-diagnosed DNS situation there is. dig talks to a resolver over the network directly. Your application goes through the resolver library — which reads /etc/hosts first, may consult mDNS, may talk to a local stub resolver with its own cache, and honors nsswitch.conf.
So dig returning the right answer while the application gets the wrong one is not a contradiction, it’s the diagnosis: something in NSS is answering before DNS is consulted. Nine times in ten it’s a line in /etc/hosts that somebody added during an incident in 2024 and never removed. getent hosts is the command that finds it, because it resolves the way your application does.
The flags for daily use
dig +short example.com A # just the answer
dig +short example.com NS # who's authoritative
dig +noall +answer example.com A # answer section with TTLs, no noise
dig -x 203.0.113.10 # reverse
dig example.com SOA +short # serial — did the zone actually change?
dig @ns1.example.com example.com AXFR # zone transfer, if they'll let you
The SOA serial is the fastest way to check a change committed. Query every authoritative nameserver for the SOA and compare serials:
for ns in $(dig +short example.com NS); do
printf "%-30s %s\n" "$ns" "$(dig +short @"$ns" example.com SOA | awk '{print $3}')"
done
Mismatched serials mean your secondaries haven’t transferred yet, which is a different problem from caching and has a different fix. Clients hitting different secondaries get different answers, deterministically, forever — until the transfer completes. That’s the shape of “half the estate sees the new value.”
The TTL discipline
The reason DNS changes hurt is almost always that nobody planned them:
- Lower the TTL to 60 seconds at least one old-TTL-period before the change. If the record has been at 86400 for a year, you must drop the TTL and then wait a full day for the old TTL to age out of every cache before the low value is universally in effect. Skip this and the low TTL is itself stuck behind the high one.
- Make the change. Now the maximum staleness is 60 seconds.
- Verify with
+traceand+norecurseagainst your own resolvers. - Put the TTL back up a day later.
Skipping step 1 is why cutovers turn into all-night events, and it’s the part that has to happen before anyone schedules the maintenance window.
The one that isn’t DNS
Negative caching. A NXDOMAIN gets cached too, for the duration in the SOA minimum field — not the record’s TTL, because the record doesn’t exist. Create a record that somebody queried before it existed and they’ll keep getting NXDOMAIN for however long that SOA field says, which on a lot of zones is an hour or more.
dig example.com SOA +noall +answer
Last number in the record. That’s your negative TTL. It has burned every single person who created a record only after the client started asking for it — which is to say, everyone, at least once.