DNS Troubleshooting with dig: A Workflow for Real Failures
A systematic dig-based workflow for diagnosing DNS problems — isolating resolver vs authoritative faults, reading NXDOMAIN vs SERVFAIL, tracing delegation, catching stale caches, and debugging split-horizon and DNSSEC failures.
"It's Always DNS" Is Not a Diagnosis
The joke is funny because DNS failures present as everything except DNS. An application timeout, a TLS error, a mail bounce, a service that works from one host and not another. By the time it reaches you it looks like a network fault or a broken app.
The reason DNS is hard to debug is not the protocol — it is that a single name resolution involves several independent caches and at least two classes of server, and the failure could be in any of them. ping tells you it failed. It does not tell you where.
This post is the workflow I actually use, in order. Each step eliminates a layer.
Related: How DNS Works — Complete Guide — the resolution mechanics this workflow assumes.
Step 0: Read the Status Code First
Before anything else, get the response code. It narrows the problem more than any other single fact.
dig example.com
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42137
;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^| Status | Meaning | Where to look |
|---|---|---|
NOERROR + answer | Working | Not DNS — look at the app, routing, or TLS |
NOERROR + no answer | Name exists, no record of that type | You asked for the wrong record type |
NXDOMAIN | Name definitively does not exist | Typo, or record was never created / was deleted |
SERVFAIL | Resolver tried and failed | Upstream broken, DNSSEC failure, or firewall |
REFUSED | Server declined to answer | ACL / not authoritative / recursion disabled |
| timeout | No response at all | Port 53 blocked, wrong server IP, server down |
The distinction that saves the most time is NXDOMAIN vs SERVFAIL.
NXDOMAINis an authoritative answer: the zone was reached and the name is not in it. Stop looking at infrastructure — look at the record.SERVFAILmeans the resolver could not get a trustworthy answer. The zone might be fine. Look at infrastructure, DNSSEC, or the resolver.
Treating a SERVFAIL as "the record is missing" sends people editing zone files for an hour when the actual fault is a blocked port.
Step 1: Is It the Resolver or the Zone?
Ask a different resolver. This one question splits the problem in half.
# What your configured resolver says
dig example.com
# What a public resolver says
dig @1.1.1.1 example.com
dig @8.8.8.8 example.com| Local | Public | Conclusion |
|---|---|---|
| fails | works | Your resolver or network — stop looking at the zone |
| works | fails | Usually stale local cache, or split-horizon DNS |
| fails | fails | Genuine zone/delegation problem |
| works | works | Not resolution — check the app, or you already fixed it |
Step 2: Ask the Authoritative Server Directly
Caches lie — that is their job. To see the truth, query the authoritative nameserver and bypass recursion entirely.
# Who is authoritative?
dig +short NS example.com
# ns1.example-dns.net.
# ns2.example-dns.net.
# Ask it directly, with no recursion
dig @ns1.example-dns.net example.com A +norecurse+norecurse matters. Without it you may get a cached answer from that server's own resolver side rather than its authoritative data.
Look for the aa flag in the response — it means authoritative answer:
;; flags: qr aa rd; QUERY: 1, ANSWER: 1
;; ^^ authoritative
If the authoritative server has the correct record but resolvers return something else, you have a caching/TTL problem. If the authoritative server itself is wrong or empty, it is a zone content problem. Two completely different fixes.
Check every nameserver, not just the first
This catches a genuinely nasty class of intermittent failure:
for ns in $(dig +short NS example.com); do
echo "--- $ns"
dig @"$ns" example.com A +norecurse +short
doneInconsistent answers between nameservers produce failures that appear random — roughly one request in N, depending on which server the resolver happened to pick. Symptoms look like flaky networking, not DNS. Usually it is a zone transfer that silently stopped, leaving one secondary stale.
Step 3: Trace the Delegation
If the zone itself looks fine, verify the path to it from the root.
dig +trace example.comThis walks root → TLD → authoritative, showing each referral. What to look for:
- Where it stops. The last successful step is the boundary of the problem.
- NS records at the parent vs the child. These must agree.
A delegation mismatch is one of the most common "the record is right but nothing works" causes:
# What the parent (TLD) says the nameservers are
dig @a.gtld-servers.net NS example.com +norecurse
# What the zone itself says
dig @ns1.example-dns.net NS example.com +norecurseIf the registrar still points at nameservers you migrated away from, the zone is perfect and unreachable at the same time. Fix that at the registrar, not in the zone file.
Also confirm glue records resolve — nameservers named inside the zone they serve need working A records at the parent, or resolution deadlocks.
Step 4: Stale Caches and TTL
If authoritative data is correct but clients still see the old value, work down the cache chain.
# Remaining TTL — counts down as it sits in cache
dig example.com | grep -A1 ';; ANSWER SECTION'
# example.com. 238 IN A 203.0.113.10
# ^^^ 238 seconds left before revalidationQuery the same name twice a few seconds apart. A falling TTL means you are being served from cache; the value resets to the zone's configured TTL when it is refetched.
Flushing, per layer:
# Linux (systemd-resolved)
sudo resolvectl flush-caches
# Linux (nscd)
sudo systemctl restart nscd
# macOS
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
# Windows
ipconfig /flushdns
# Chrome keeps its own cache:
# chrome://net-internals/#dns -> Clear host cacheThat last one catches people out regularly — the OS cache is clear, dig is correct, and only the browser is still wrong.
Plan TTLs before a migration. Lower the TTL to 300 seconds at least 24–48 hours before a cutover, so caches worldwide have already picked up the short value. Lowering the TTL at cutover time does nothing for records already cached at the old long value.
Step 5: The Cases That Waste the Most Time
Searching for the wrong record type
dig example.com A # no answer, NOERROR
dig example.com CNAME # there it is
dig example.com ANY # broad look (often refused/filtered)NOERROR with an empty answer section means the name exists but has no record of the type you asked for. That is not a failure — it is a different question.
Split-horizon DNS
Internal and external resolvers deliberately return different answers. A name resolving to a private address off-VPN, or a public address on-VPN, is often working exactly as designed.
dig @10.0.0.53 intranet.example.com +short # internal view
dig @1.1.1.1 intranet.example.com +short # external viewDifferent answers here are expected. Before "fixing" anything, confirm which view the failing client is supposed to be using.
DNSSEC validation failure
A classic SERVFAIL-that-is-not-your-zone. If validation is broken, a validating resolver refuses the answer while a non-validating one serves it happily:
# Validating resolver: SERVFAIL
dig @1.1.1.1 example.com
# Same query with validation disabled: works
dig @1.1.1.1 example.com +cd # +cd = checking disabledIf +cd fixes it, you have a DNSSEC problem — usually an expired signature or a DS record at the parent that no longer matches the zone's key after a rollover. The zone data is fine; the chain of trust is not.
Port 53 filtered
dig @8.8.8.8 example.com +tcp # works
dig @8.8.8.8 example.com # times outUDP 53 blocked while TCP passes. Common on restrictive networks, and it breaks large responses first — which is why DNSSEC-signed zones and lookups with many records fail while simple ones succeed. Confusingly intermittent until you spot the pattern.
The Workflow, Condensed
1. Read the status code
NXDOMAIN -> record does not exist. Check the record.
SERVFAIL -> resolver could not validate. Check infra/DNSSEC.
timeout -> port 53 path. Check firewall, try +tcp.
2. dig @1.1.1.1 <name>
differs from local -> your resolver / cache / split-horizon
same as local -> continue
3. dig @<authoritative-ns> <name> +norecurse
correct here, wrong elsewhere -> caching / TTL
wrong here -> zone content
Loop over ALL nameservers to catch stale secondaries.
4. dig +trace <name>
Compare parent NS vs child NS. Mismatch -> fix at registrar.
5. Still stuck:
- wrong record type? (NOERROR + empty answer)
- split horizon? (compare internal vs external resolver)
- DNSSEC? (+cd makes it work)
- UDP blocked? (+tcp makes it work)
Worth writing on a card: query the authoritative server directly before changing anything. Most wasted DNS debugging is spent editing zones that were already correct, because the evidence came from a cache instead of the source.
Frequently Asked Questions
What is the difference between NXDOMAIN and SERVFAIL?
NXDOMAIN is an authoritative statement that the name does not exist — the zone was reached and queried successfully. SERVFAIL means the resolver could not obtain a trustworthy answer at all, which points at infrastructure, DNSSEC validation, or blocked traffic rather than a missing record. They lead to completely different investigations.
What does dig +trace actually show?
It performs the resolution iteratively from the root, printing each referral: root servers → TLD servers → the zone's authoritative servers. It is the fastest way to see where resolution breaks and to spot delegation mismatches between what the parent zone publishes and what the child zone says about itself.
Why does dig work but my application still fail?
Common causes: the application uses a different resolver or a different network namespace (containers frequently do); it caches DNS in-process for the life of the connection pool; or your browser holds its own cache separate from the OS. dig bypasses all application-level caching, so a mismatch usually points at a cache above the OS layer rather than at DNS.
How long should I wait for DNS changes to propagate?
Roughly the old record's TTL, since resolvers keep serving the cached value until it expires. There is no global push. Lower the TTL well before a planned change — a day or two ahead — so caches have already adopted the shorter value by cutover.
Why does adding +tcp fix my DNS query?
Because UDP port 53 is being blocked or truncated somewhere in the path while TCP 53 passes. This tends to surface first with large responses, such as DNSSEC-signed answers or names with many records, which makes it look intermittent until you notice small queries always work and big ones never do.