DNS Failures: NXDOMAIN, SERVFAIL, Slow Lookups and Split-Horizon Traps
"DNS is broken" is never one problem. The response code, the resolver chain and the TTLs each tell a different story. This guide gives you the dig workflow that separates them.
First: ask the right questions in order
- What does the client's resolver return? (
getent hosts/nslookup) - What does the resolver itself see? (query the resolver directly)
- What do the authoritative servers say? (query them directly)
- Is the answer cached wrongly somewhere in between?
dig app.example.com +short # via the configured resolver dig app.example.com @10.0.0.53 # the resolver itself dig app.example.com @ns1.example.com. # authoritative truth
When authoritative is right but the resolver is wrong, you have a caching or forwarding problem, not a DNS-record problem.
NXDOMAIN vs SERVFAIL: the two failure codes
The chain answered confidently "no such name". Causes: a typo, the record genuinely missing, a wrong zone (querying internal names against public DNS or vice versa), or a parent zone missing the child's delegation. Check the exact name with +short and then confirm where the answer came from using the authority section.
SERVFAIL means the resolver could not complete the job: broken delegation (glue missing or lame delegation), DNSSEC validation failure, all upstream servers timing out, or a forwarder that is down. DNSSEC is the classic modern cause: a mis-signed zone makes validating resolvers return SERVFAIL while non-validating tools "work".
dig app.example.com +trace # walk the delegation from the root
dig app.example.com +cdflag # disable checking: if this succeeds while
# normal query SERVFAILs, DNSSEC validation is the causeSlow lookups: the 5-second pause
A consistent ~5s delay before resolution usually means the first resolver in the list is unreachable and the client waits for its timeout before trying the second. Measure per-server:
time dig @8.8.8.8 app.example.com +time=2 +tries=1 time dig @10.0.0.53 app.example.com +time=2 +tries=1
Other common causes: reverse-lookup timeouts (apps doing PTR on every connection with dead reverse zones), an /etc/resolv.conf listing a dead internal server first, and single-label names wandering off to public DNS because the search list is empty.
The client side: /etc/resolv.conf, systemd-resolved and nsswitch
cat /etc/resolv.conf # nameserver order, search list, options resolvectl status # on systemd-resolved hosts: per-interface DNS grep hosts: /etc/nsswitch.conf # files before dns? mdns stealing lookups?
- On modern distros
/etc/resolv.confis often a stub pointing at 127.0.0.53 — the real configuration lives inresolvectlor NetworkManager; editing the file "fixes" nothing. - DHCP overwrites resolver settings on reboot — a manual edit that dies on restart is a DHCP problem, not a DNS one.
options ndots:5in Kubernetes makes external names try cluster-internal suffixes first — the famous "first query is slow" in pods; setndots:2or add a trailing dot.
Split-horizon and the wrong answer that looks like a failure
Internal users get the internal IP, external users the public one — by design. The incident version: an internal client receives the public IP for an internal service and hairpins or fails, usually because the internal zone lost the record, the client is using the wrong resolver (a hardcoded 8.8.8.8 bypassing internal DNS), or a VPN pushes no DNS settings at all. Verify with the two-resolver comparison from section one; fix the resolver assignment, not the records.
Caching: when the fix "did not work"
You corrected a record but clients still see the old IP. Follow the TTLs: the resolver cache, the OS cache (resolvectl flush-caches, ipconfig /flushdns), the browser cache, and any middlebox. A record published with TTL 86400 takes up to a day to expire everywhere — lower TTLs before planned changes, not after the incident.
dig app.example.com @resolver +norec # what does the cache-less view say? # compare with dig app.example.com
When only some clients fail
"Works on my laptop, fails on the server" usually means the two devices ask different resolvers or travel different paths. Check: VPN split-DNS pushing no internal suffixes; a container with a hardcoded nameserver 8.8.8.8; IPv6 — a client that prefers AAAA will time out on networks where the IPv6 path is black-holed, then fall back after a painful delay (the "first request is slow" web symptom; test with dig AAAA and a v4-only query). And anycast/cloud DNS: the resolver answering from another region may have a stale view of a zone that was transferred minutes ago — query the primary authoritative directly to be sure propagation is the story.
A bare example.com cannot be a CNAME per the RFC; providers fake it with ALIAS/ANAME records, and hand-rolled zone files that try it produce SERVFAIL or NXDOMAIN at odd places. If apex resolution misbehaves after a provider migration, inspect how the apex record is implemented — it should be A/AAAA (or the provider's alias type), never a literal CNAME.
Prevention checklist
- Monitor from the client view: query your resolvers for critical names, not just the authoritative servers.
- Keep two healthy resolvers in client config; a dead first entry is the classic slow-network complaint.
- Before migrations, drop TTLs to 300 a day ahead.
- Alert on SERVFAIL rate at the resolver — it spikes early in DNSSEC and delegation problems.
- Document which zones are split-horizon and which resolver each network segment must use.