Chapter 14 · Part V · In the field

Debugging DNS with dig

When a name will not resolve, the answer to "why" is almost always sitting in plain sight in the output of one command, if you know how to read it. The first moves, checking the gateway and then splitting the problem into "network" or "DNS," are in networks · The troubleshooting toolkit, and what a resolver is lives in networks · DHCP & DNS. This chapter starts where those stop: every line of a dig answer, the flags that matter, how to ask the server that actually knows, how to follow a delegation down the tree, and a field guide to the five ways DNS says no.

4sections in every DNS message after the header: question, answer, authority and additional (RFC 1035 §4.1)
0 · 2 · 3 · 5the response codes you will meet most: NOERROR, SERVFAIL, NXDOMAIN and REFUSED
25reason codes, 0 to 24, defined by Extended DNS Errors (RFC 8914, 2020), so a failure can say why it failed

Common mix-up: an empty answer is not NXDOMAIN. If you ask for a name's IPv6 address and get status: NOERROR with ANSWER: 0, the name exists and simply has no record of that type. That is NODATA, and it has no response code of its own: you recognize it by the empty answer section and the SOA record in the authority section (RFC 2308). NXDOMAIN, code 3, means the name itself does not exist. Mixing them up sends you to fix the wrong thing.

Read it top to bottom

The anatomy of a dig answer

dig ("domain information groper") ships with BIND, the DNS software from ISC, and is on nearly every Mac and Linux machine. Its virtue is that it hides nothing. Your browser, your operating system and ping all ask the DNS through layers of caching and retry logic and tell you only "it worked" or "it didn't." dig sends exactly one question to exactly one server and prints the reply it got back, almost byte for byte, with every field labeled.

Here is a complete answer. Everything below this point in the chapter is simulated, with reserved names (example.test, example.net) and documentation addresses (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24), but the format is exactly what BIND 9's dig prints.

; <<>> DiG 9.18.28 <<>> www.example.test A
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41865
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;www.example.test.              IN      A

;; ANSWER SECTION:
www.example.test.       2400    IN      A       192.0.2.10

;; Query time: 2 msec
;; SERVER: 192.0.2.53#53(192.0.2.53) (UDP)
;; WHEN: Thu Oct 22 10:20:00 UTC 2026
;; MSG SIZE  rcvd: 61

Read it in the order it is printed and ask one question of each part.

The header line: did it work? status is the response code, the single most important word on the page. opcode: QUERY is nearly always what you see. The id is a random 16-bit number dig put in its question; the reply has to carry the same one or dig ignores it.

The flags: who answered, and how? These are one-bit switches in the header, and they are how you tell a cached answer from a source answer and a validated answer from an unchecked one. The counts after them say how many records are in each section.

The OPT pseudosection: what was negotiated? Modern DNS rides on EDNS (RFC 6891), an extension carried as a fake record in the additional section. That is why ADDITIONAL says 1 even though no additional records are printed: the one is the OPT record. It says how large a UDP reply the sender can accept (here 1,232 bytes), whether DNSSEC records were wanted (the do flag) and, when something fails, an Extended DNS Error with a reason.

The sections: what did it say? The question is echoed back with a leading semicolon, because it is not a record. Then each record in the answer, authority and additional sections, one per line: name, TTL, class, type, data.

The footer: who said it, and how fast? SERVER is the address that actually replied and whether it came over UDP or TCP. A query time of 1 or 2 milliseconds almost always means a cache near you; tens of milliseconds means a trip across the internet; seconds means something timed out and was retried.

FlagNameSet meansWhat it tells you when debugging
qrquery responseThis message is a replyAlways set in what dig prints; it is how the server marks its answer
aaauthoritative answerThe server answering holds the zoneYou reached the source. No aa from a server you think is authoritative is a red flag (a lame server, or the wrong server)
tctruncatedThe reply did not fit in UDPdig retries over TCP automatically; if TCP is blocked, the lookup fails (RFC 7766)
rdrecursion desiredThe asker wants the server to go and find the answerSet by default; +norecurse clears it, which is what you want when asking an authoritative server
rarecursion availableThis server will recurse for youSet on resolvers, clear on authoritative servers. Missing on a resolver means it refused to recurse for you
adauthentic dataThe resolver validated the answer with DNSSECOnly meaningful from a validating resolver you trust. Absent on an unsigned zone; absent with SERVFAIL on a broken one
cdchecking disabledThe asker said "do not validate for me"+cd sets it. If a query fails without it and succeeds with it, the fault is DNSSEC
Go deeper: where the flags live in the bytes

The 12-byte header is the subject of Chapter 2. The short version: the third and fourth bytes are a 16-bit word holding qr, a 4-bit opcode, aa, tc, rd, ra, a reserved bit, ad, cd and the 4-bit RCODE (RFC 1035 §4.1.1, with ad and cd added by RFC 4035 §3.2). Four bits of RCODE only reach 15, so EDNS adds eight more high bits in the OPT record (RFC 6891 §6.1.3); that is how a code like BADVERS (16) exists. dig glues the pieces together and prints the name.

The do ("DNSSEC OK") bit is not a header flag at all. It lives in the OPT record's flags, which is why dig prints it on the EDNS line and not on the flags line.

Instrument 1

The dig output reader

Pick a sample. Every underlined word is live: hover it, tap it, or tab into the output and use the arrow keys, and the box underneath explains exactly that piece, with any arithmetic done for you (how long a TTL really is, how the negative-cache time is worked out, whether the reply would have fitted in UDP). Then switch on the quiz and say what the output is telling you. The samples are generated by the same small simulator that drives the diagnosis game further down, so sizes and counts are computed, not typed in. All data is simulated.


      
Point at any underlined part

Start with the word after status:, then the flags.

Keyboard: focus the output, then ← → step through parts, ↑ ↓ jump a line. Score so far: 0 of 0.

Choose the question

Ask the right server

A plain dig www.example.test asks whichever resolver your machine is configured with, and that resolver answers from its cache whenever it can. That is the right first question, because it is the answer your users get. It is almost never the last one. The single most useful habit in DNS debugging is to name the server you are asking with @, and to ask several in turn: your resolver, a different resolver, and each authoritative server for the zone. Where their answers differ is where the fault is.

When you ask an authoritative server, add +norecurse. You want to know what that server itself holds, not what it might go and fetch if it happens to also be a resolver. A healthy reply has aa set, no ra, and the TTL exactly as configured in the zone rather than a number counting down.

OptionWhat it doesReach for it when
@serverSend the question to this server instead of the system resolverAlways, once you are past the first look. Compare servers
+norecurseClear rd: "tell me only what you hold"Asking an authoritative server, or peeking at a resolver's cache
+traceDo the resolution yourself, from the root down, printing each referralThe delegation itself is in doubt: wrong NS, lame server, missing glue
+shortPrint only the answer dataScripts and quick checks. It hides the status, so never use it to diagnose a failure
+dnssecSet the do bit and show RRSIG and other DNSSEC recordsChecking that a zone is signed and its signatures are current
+cdAsk the resolver not to validateA SERVFAIL you suspect is DNSSEC: if +cd fixes it, it is
-x addrReverse lookup: builds the in-addr.arpa or ip6.arpa name for youChecking PTR records (Chapter 7)
+tcpUse TCP from the startLarge answers, or testing whether TCP port 53 gets through a firewall
+multilineSplit long records over several lines with commentsReading SOA timers and DNSKEY key tags

The other tools in the box

delv, also from BIND, resolves a name and then validates the DNSSEC chain itself, printing either ; fully validated or a reason it could not. It is the tool to use when you need to know why validation failed rather than just that a resolver returned SERVFAIL.

Resolve-DnsName is the PowerShell equivalent on Windows. Resolve-DnsName www.example.test -Server 192.0.2.1 -Type A asks a named server; -NoRecursion clears rd; -DnsOnly skips the hosts file and other local name sources; -DnssecOk asks for signatures. Its output is a table rather than raw sections, but the same reasoning applies.

nslookup is everywhere, and it will do in a pinch, but it hides the things you most need. It does not print the flags, so you cannot see aa, ad or tc. It reports a NODATA reply and an NXDOMAIN in ways that are easy to confuse. Its own reverse lookup of the server's address prints a confusing error at the top when that address has no PTR record, which looks like a failure when nothing is wrong. Use it to confirm, not to diagnose.

Go deeper: which resolver am I actually using?

The SERVER line is the honest answer, but only for the query dig made. Your applications may use something else. On macOS, scutil --dns lists every resolver configuration, including per-domain ones a VPN adds. On Linux with systemd-resolved, resolvectl status shows which servers each network link uses, and plain dig there talks to the local stub at 127.0.0.53, which forwards on. On Windows, Get-DnsClientServerAddress lists them per adapter, and Get-DnsClientNrptPolicy shows name-based routing rules that VPNs install.

This matters most for split horizon (Chapter 10): the right answer may exist only on a resolver your laptop is not using.

Five ways to say no

NXDOMAIN, NODATA, SERVFAIL, REFUSED and silence

When a lookup fails, the first job is to classify the failure, because each kind points somewhere different. The response code is a four-bit number in the header (RFC 1035 §4.1.1, registry kept by IANA), and only a handful of its values show up in practice.

What dig showsCodeMeansLook next at
NOERROR + answer0Here is your dataIs it the data you expected, from the server you meant? Check TTL and SERVER
NOERROR, ANSWER: 00NODATA: the name exists, but has no record of this typeThe type you asked for. An AAAA query on an IPv4-only name is the usual case
NXDOMAIN3The name does not exist, and nothing exists below it (RFC 8020)Spelling, the zone file, a stale negative cache, or a view that lacks the name
SERVFAIL2The resolver tried and could not get a trustworthy answerDNSSEC (try +cd), unreachable or lame authoritative servers. Read the EDE line
REFUSED5This server will not answer this question for youYou are asking an authoritative server for a zone it does not serve, or a resolver that does not serve your network
FORMERR1The server could not parse the questionOld or broken middleboxes and servers, often choking on EDNS
timed outnoneNo reply at allReachability: the address, a firewall, port 53, or a server that is down

Negative answers, NXDOMAIN and NODATA alike, carry the zone's SOA record in the authority section. That is not decoration. RFC 2308 says a resolver may cache the "no" for the lower of that SOA record's TTL and its last field, MINIMUM, and the TTL dig prints on the SOA line is how many seconds of that are left. When someone creates a record and a colleague still gets NXDOMAIN, this number is how long they will keep getting it (Chapter 4 has the full story).

; NODATA: the name exists, there is just no AAAA record
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3301
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; AUTHORITY SECTION:
example.test.   3600  IN  SOA  ns1.example.test. hostmaster.example.test. 2026102201 7200 900 1209600 3600

; NXDOMAIN: no such name at all (a typo with four w's)
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 3302
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; AUTHORITY SECTION:
example.test.   3600  IN  SOA  ns1.example.test. hostmaster.example.test. 2026102201 7200 900 1209600 3600

Extended DNS Errors: SERVFAIL with a reason

For thirty years SERVFAIL meant only "something went wrong," and the something could be an expired signature, a server that did not answer, or a resolver policy. RFC 8914 (2020) added an EDNS option that carries a numeric reason and an optional line of text. dig prints it in the OPT pseudosection, and resolvers that support it, including the big public ones, fill it in. A few you will meet:

EDENameTypical cause
3Stale AnswerThe answer came from an expired cache entry because the source could not be reached (serve-stale)
6DNSSEC BogusValidation failed: a signature did not check out
7Signature ExpiredThe zone's RRSIGs ran past their end date: re-signing stopped
9DNSKEY MissingA DS at the parent names a key the child does not publish: the classic "moved provider, left the DS behind"
15–18Blocked, Censored, Filtered, ProhibitedResolver policy, not a fault in the zone
22No Reachable AuthorityNone of the zone's servers answered usefully
23Network ErrorA network-level failure reaching an authoritative server
Go deeper: NXDOMAIN with an answer in it

One combination surprises people: status: NXDOMAIN together with a CNAME record in the answer section. That is correct. The response code describes the last name in the chain, not the one you asked for. Your name existed and pointed elsewhere; the name it pointed to does not exist. Read the CNAME's target carefully: the fault is there.

The same logic applies to NODATA through a CNAME: NOERROR, the CNAME in the answer, and an SOA in the authority section that belongs to the target's zone, which may not be yours at all.

A method, not a hunch

Compare, then narrow

Every DNS fault is a disagreement: between what you think the zone says and what it says, between the parent and the child, between one cache and another. The method is to make those parties answer the same question side by side until you find the pair that disagree.

  1. Reproduce with dig, and read the status. Ask your resolver. NOERROR with the wrong data, NXDOMAIN, NODATA, SERVFAIL, REFUSED and a timeout are six different problems. Note the TTL and the query time.
  2. Ask each authoritative server directly with @ns1 … +norecurse, then @ns2, and so on through the whole NS set. Every one should say aa and give the same answer and the same SOA serial. One that says REFUSED, answers without aa, or times out is your fault.
  3. Compare the parent's NS set with the child's. The parent zone (for example.test, the test servers) holds the delegation; the child holds its own NS records. They should list the same servers. A server only the parent lists, which no longer serves the zone, is a lame delegation (Chapter 6). +trace shows you the parent's side.
  4. Compare resolvers. If the authoritative servers are right and one resolver is wrong, it is a cache: look at the TTL on its answer, or on the SOA in its negative answer, and wait or flush. If only one network sees a different answer, look for split horizon.
  5. Check DS against DNSKEY. A SERVFAIL that disappears with +cd is DNSSEC. Ask the parent for DS and the child for DNSKEY: the DS key tag must match a key the child publishes (Chapter 11). delv and DNSViz draw the whole chain.
  6. Flush the right cache, last. Only once you know the source is right. Flushing your laptop's cache does nothing for a colleague whose resolver holds the old answer, and nothing at all if the authoritative data is still wrong.
Go deeper: which flush reaches which cache

Your browser, your operating system, your router and your resolver each keep their own copy (Chapter 4 draws the stack). sudo killall -HUP mDNSResponder on macOS, resolvectl flush-caches with systemd-resolved and Clear-DnsClientCache or ipconfig /flushdns on Windows clear only the operating system's layer. A recursive resolver's cache can be cleared only by whoever runs it; some public resolvers offer a web form for one name. dig itself has no cache, which is one more reason to debug with it rather than with a browser.

Instrument 2

The diagnosis game

A user reports a symptom. You have a laptop with dig and the network described in the case. Choose your next command from the menu; the simulator works out the reply from a model of the zone, its servers, its parent, three resolvers and their caches, and prints it in dig's format. Every word in the replies is explainable, exactly as in the reader. When you think you know, name the fault. Fewer commands and fewer wrong guesses score better. Everything is simulated: names under example.test, documentation addresses. Lines beginning ; note: are this page's annotations, not dig's.

Commands used0
Wrong guesses0
Statusinvestigating

Run a command

Then point at any underlined part of its output.

Follow the chain

Reading a +trace

A normal query hides the resolver's work. dig +trace does that work itself, in front of you. It asks your resolver only for the root servers' names, then asks a root server about your name and prints the referral it gets (the NS records for test), asks one of those servers and prints the next referral (the NS records for example.test, plus the DS record if the zone is signed), and finally asks one of the zone's own servers. After each step it prints a ;; Received … bytes from … line saying which server it heard from. Chapter 3 explains the walk itself; here the point is reading it.

Two things make a trace powerful. It shows the delegation as the parent publishes it, which is the version resolvers actually use to find you, and which your own zone file cannot fix. And it ignores every cache, so a trace that works while users fail points at caches, and a trace that fails points at the zone or its delegation.

It has limits. It picks one server at each level, at random, so a fault on one of two servers shows up only some of the time: run it more than once, or ask each server directly. It does not validate DNSSEC; use delv for that. And it does not follow CNAMEs into other zones.

The instrument below runs the same trace against a healthy zone and a broken one and lines them up. To keep the comparison fair, both traces ask the zone's servers in the same order, starting with ns2.

Instrument 3

Spot the divergence

Choose a fault. The simulator builds two copies of the example.test world, identical except for that one fault, runs dig +trace +dnssec against each, and diffs the output line by line. Lines that differ are highlighted, the first difference is outlined, and the ladder shows which hop went wrong. The validation line underneath is what a validating resolver would conclude. Simulated data throughout.

Healthy

Broken

Healthy: validation
Broken: validation
Lines that differ
Case files

Seven faults you will meet

Each of these is a real, common failure, shown on the simulated example.test zone. Each is also a case in the diagnosis game, where you can find it yourself. The theory behind each fault lives in its own chapter; this is how it looks from the outside.

1 · The missing trailing dot

In a zone file, a name without a final dot is relative: the zone's origin is appended. Someone types www IN CNAME web.example.test and the server reads it as web.example.test.example.test., which does not exist. The resolver follows the CNAME and gets NXDOMAIN, with your CNAME sitting in the answer section to show you the doubled name. RFC 1912 (§3.2) calls out exactly this. Fix: add the dot, bump the serial, reload.

;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 7710
;; ANSWER SECTION:
www.example.test.  3600  IN  CNAME  web.example.test.example.test.

2 · The lame delegation

The parent lists ns1 and ns2; you moved off ns2 months ago and the zone's own NS records now say ns1 and ns3. Resolvers trust the parent's list to get started, so some of them ask ns2 first, get REFUSED or a non-authoritative reply, and try again elsewhere. Users see slow first loads; under load, the odd SERVFAIL. Ask each listed server directly with +norecurse and compare the parent's NS set with the child's (Chapter 6; RFC 1912 §2.8). Fix: update the delegation at the registrar.

3 · The DS left behind

A signed zone moves to a new DNS provider that does not sign it, or signs it with new keys, and nobody removes or replaces the DS record at the registrar. Every validating resolver now sees a parent saying "this zone is signed with key 2371" and a child that publishes no such key, and refuses the answer: SERVFAIL, EDE 9 (DNSKEY Missing). The provider's dashboard looks perfect because the authoritative servers are fine. +cd makes the error vanish, which is the tell. Fix: publish a DNSKEY that matches, or remove the DS (Chapter 11).

4 · The CNAME at the apex

A CNAME says "this name is an alias, and has no other records." The zone apex must have SOA and NS records, so a CNAME there breaks the rules (RFC 1912 §2.4). Strict servers refuse to load such a zone; lenient ones serve it, and then a resolver asking for the apex's MX follows the CNAME into someone else's zone, finds no MX, and returns NODATA. The website works; mail bounces. Fix: an A/AAAA record at the apex, or a provider's flattening feature, or an HTTPS record (Chapter 5).

5 · The negative cache after creating a record

Someone looked up shop.example.test before it existed. Their resolver cached the NXDOMAIN for min(SOA TTL, MINIMUM), here an hour. You create the record; it works for you, because your resolver never asked; it keeps failing for them until the timer runs out. The SOA's TTL in their negative answer shows exactly how long is left (Chapter 4). Fix: wait, or flush that resolver; next time, lower the SOA MINIMUM before launches.

6 · Split horizon and the VPN

app.example.test exists only in the internal view of the office resolver. From home, with the VPN up, it does not resolve, because the VPN carries your traffic but did not change which resolver your laptop asks. Your resolver and the public authoritative servers both say NXDOMAIN, correctly, for the public view. Asking the office resolver by address answers at once. Check what your system actually uses (scutil --dns, resolvectl status, Get-DnsClientNrptPolicy) and fix the VPN's DNS settings (Chapter 10).

7 · Truncated over UDP, blocked over TCP

A zone's TXT records have grown with years of verification tokens, and the reply is now larger than the 1,232-byte UDP size most software advertises. The resolver answers with the tc flag set and dig prints ;; Truncated, retrying in TCP mode. A firewall on the office network allows only UDP on port 53, so the TCP retry times out. Small lookups work; big ones fail. RFC 7766 makes TCP support mandatory for DNS: the fix is to allow TCP/53, and secondarily to prune the records (Chapter 2).

An old list that still holds

Common errors, RFC 1912

In 1996 David Barr wrote down the configuration mistakes he kept seeing. Nearly every one is still on the list of things to check, and several of them are case files above.

RFC 1912MistakeWhat dig shows
§2.1A record and PTR that do not match, or a missing PTRdig -x returns NXDOMAIN or a different name
§2.2SOA serial not increased after an editSecondaries never pick up the change: different answers from ns1 and ns2, same serial
§2.3Glue that is wrong or out of date+trace shows the parent's glue address timing out
§2.4CNAME alongside other data, CNAME at the apex, MX or NS pointing at a CNAMENODATA through a CNAME, or a zone the server refuses to load
§2.8Lame delegationREFUSED, or an answer without aa, from a server the parent lists
§3.2Missing trailing dotThe zone's name appearing twice in a target
Go deeper: let a tool check the chain

For DNSSEC in particular, a picture beats a page of dig. DNSViz, a free web tool, queries every server at every level of the chain and draws the delegations, keys and signatures as a graph, with each problem flagged. It is the fastest way to confirm a DS/DNSKEY mismatch or an expired signature on a public zone. The same project publishes the command-line tool behind the site.

Cheat sheet

Terms from this chapter

dig
BIND's command-line query tool: sends one question to one server and prints the full reply, every field labeled.
Response code (RCODE)
The number in a reply's header that says how the query went: 0 NOERROR, 1 FORMERR, 2 SERVFAIL, 3 NXDOMAIN, 5 REFUSED.
aa flag
Authoritative Answer: set when the replying server holds the zone. Its absence from a supposed authoritative server signals a lame or wrong server.
ad flag
Authentic Data: set by a validating resolver when the answer passed DNSSEC validation.
OPT pseudosection
dig's view of the EDNS OPT record: version, UDP buffer size, the do bit and any Extended DNS Error.
Extended DNS Error (EDE)
An EDNS option (RFC 8914) that adds a numbered reason, such as 9 "DNSKEY Missing," to a failure.
+trace
A dig mode that resolves from the root itself, printing each referral and which server sent it.
Lame delegation
A server named in a delegation that does not answer authoritatively for the zone.
delv
BIND's validating lookup tool: resolves a name and checks the DNSSEC chain itself, saying why validation failed.
Where this comes from

Sources