OnCallReady

Lesson 8.18 · Addressing & DNS · 26 min read

Reading dig properly

In plain words

Asking dig a question is like sending a letter to an information office and getting back a reply form with boxes: a stamp saying "found" or "no such person" (the status), who wrote the reply (SERVER), whether they are the official record-keeper or just repeating what they heard (the aa flag), and a "valid until" timer on each answer (the TTL).

Most people read only the answer and skip the rest. This lesson teaches you to read the whole form. NOERROR with an empty answer is different from NXDOMAIN. A TTL that counts down between two queries means you are reading a cached copy. And the SERVER line tells you whether you asked resolved at 127.0.0.53 or the real server you meant.

Why this matters

dig is the tool you reach for in every DNS problem, and its output is dense. Most wrong conclusions come from reading only the answer and skipping the header: "no such name" and "name exists, but not that kind of record" look almost the same. This lesson reads the whole thing.

What you need to know already: 8.16 - resolvers, the stub at 127.0.0.53, authoritative servers, TTLs.

dig (domain information groper) sends one DNS question and prints the whole reply. dig NAME asks the server from resolv.conf; dig @SERVER NAME asks a server you choose.

A full answer, line by line

$ dig example.com

; <<>> DiG 9.20.11-1ubuntu2-Ubuntu <<>> example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43522
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;example.com.			IN	A

;; ANSWER SECTION:
example.com.		86400	IN	A	93.184.216.34

;; Query time: 31 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Wed Sep 23 10:12:01 UTC 2026
;; MSG SIZE  rcvd: 56

Lines starting with ; are comments dig adds. The parts that matter:

Ask again, and watch the TTL

+noall +answer = hide everything (+noall), then show only the answer section (+answer):

$ dig +noall +answer example.com
example.com.		7196	IN	A	93.184.216.34

The TTL went down: this answer came from resolved's cache, which reports how long it will still keep the record (resolved caps cached TTLs at two hours, so 86400 became at most 7200). Ask the zone's own server and you get the full TTL and the aa flag:

$ dig @10.0.3.53 api.lab
...
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
...
api.lab.		30	IN	A	10.0.3.20

(ra is missing: a server that only holds its own zone does not do the chain for others.) A TTL that counts down is a cached copy; a TTL that stays at its full value on every query comes from the source. That one observation answers "is this a cache problem?".

The status codes

NOERROR    the server answered. Check ANSWER: it may still be empty!
NXDOMAIN   the NAME does not exist (for any type)
SERVFAIL   the resolver tried and failed: the zone's servers are down or
           unreachable, or the chain is broken
REFUSED    this server will not answer you: it does not hold that zone and
           will not look it up for you

NXDOMAIN

$ dig nothing.lab
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 18231
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
...
;; AUTHORITY SECTION:
lab.			300	IN	SOA	ns1.lab. hostmaster.lab. 2026092301 7200 3600 1209600 300

The SOA record in AUTHORITY is the zone saying "I own lab., and there is no such name" (SOA = start of authority, the zone's own info record; 8.22 reads it field by field). Its last number (300) is how long resolvers will remember this negative answer.

NODATA: NOERROR with nothing in it

$ dig AAAA api.lab
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5120
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

Record types go before or after the name. AAAA asks for an IPv6 address. The name exists; it just has no AAAA record. People call this NODATA. Scripts that check only the status miss it. It matters because programs ask for AAAA and A at the same time, and a slow or broken AAAA answer can delay every connection.

SERVFAIL

$ dig broken.example
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 772
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

Your resolver is fine; it could not get an answer from the zone. Next step: dig +trace (below) to see where the chain breaks, or ask the zone's servers directly with @.

REFUSED

$ dig @10.0.3.53 example.com
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 3117
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

You asked the lab zone's own server about a zone it does not hold. Common when someone points a box's resolv.conf at such a server instead of a resolver.

No answer at all

$ dig @10.0.3.99 api.lab
;; communications error to 10.0.3.99#53: timed out
;; communications error to 10.0.3.99#53: timed out
;; communications error to 10.0.3.99#53: timed out

; <<>> DiG 9.20.11-1ubuntu2-Ubuntu <<>> @10.0.3.99 api.lab
; (1 server found)
;; global options: +cmd
;; no servers could be reached

Three tries, five seconds each: fifteen seconds. Nothing answered on port 53 - wrong IP, the server is down, or a firewall drops it. Add +time=1 +tries=1 (wait 1 second, try once) when you are probing many servers.

Exit codes - a scripting trap

$ dig nothing.lab >/dev/null; echo $?
0
$ dig @10.0.3.99 api.lab >/dev/null; echo $?
9

dig exits 0 for NXDOMAIN and SERVFAIL - it did receive a DNS reply. It exits 9 only when no server replied. A health check written as dig name && echo ok (Ch 6) is always "ok". Check the answer instead: [ -n "$(dig +short name)" ].

Getting only what you need

$ dig +short example.com
93.184.216.34
$ dig +short www.example.com
example.com.
93.184.216.34
$ dig +noall +answer www.example.com
www.example.com.	86400	IN	CNAME	example.com.
example.com.		86400	IN	A	93.184.216.34

www.example.com is a CNAME - an alias that says "look up example.com instead" (8.22). +short loses the TTLs and the status - an NXDOMAIN just prints nothing. +noall +answer keeps the records with their TTLs and is the best default for reading. Other record types:

$ dig +short MX example.com
10 mail.example.com.
$ dig +short NS example.com
a.iana-servers.net.
b.iana-servers.net.
$ dig +short TXT example.com
"v=spf1 -all"
$ dig +short SRV _postgresql._tcp.db.lab
0 5 5432 db.lab.
$ dig -x 8.8.8.8 +short
dns.google.

MX = mail server, NS = the zone's servers, TXT = free text, SRV = where a service runs (8.22 explains each). -x does a reverse lookup, address to name: dig builds the special name 8.8.8.8.in-addr.arpa. and asks for its PTR (pointer) record.

+trace: follow the chain yourself

+trace makes dig walk the chain itself, from the root down, instead of asking a resolver:

$ dig +trace github.com

; <<>> DiG 9.20.11-1ubuntu2-Ubuntu <<>> +trace github.com
;; global options: +cmd
.			517521	IN	NS	a.root-servers.net.
.			517521	IN	NS	b.root-servers.net.
;; Received 239 bytes from 127.0.0.53#53(127.0.0.53) in 1 ms

com.			172800	IN	NS	a.gtld-servers.net.
com.			172800	IN	NS	b.gtld-servers.net.
;; Received 1170 bytes from 198.41.0.4#53(a.root-servers.net) in 24 ms

github.com.		172800	IN	NS	dns1.p08.nsone.net.
github.com.		172800	IN	NS	ns-1283.awsdns-32.org.
;; Received 340 bytes from 192.5.6.30#53(a.gtld-servers.net) in 28 ms

github.com.		60	IN	A	140.82.121.3
;; Received 56 bytes from 198.51.44.8#53(dns1.p08.nsone.net) in 31 ms

Each block is one step: what that server said, then Received ... from who said it. First the list of root servers, then the root points to the com servers, those point to github.com's servers, and those answer. Handing a name down like this is called delegation. +trace skips every cache, so it shows what the authoritative servers say right now. If +trace gives the new IP and plain dig the old one, it is caching.

It only works for names the public root knows:

$ dig +trace api.lab
...
lab.			86400	IN	SOA	a.root-servers.net. nstld.verisign-grs.com. 2026092300 1800 900 604800 86400
;; Received 104 bytes from 198.41.0.4#53(a.root-servers.net) in 24 ms

The root has never heard of a lab TLD. Internal zones are served by internal servers that your resolver is set up to use. For those, go straight to the authoritative server with @.

host and nslookup

Two older tools that ask the same questions with friendlier output:

$ host example.com
example.com has address 93.184.216.34
example.com has IPv6 address 2606:2800:21f:cb07:6820:80da:af6b:8b2c
example.com mail is handled by 10 mail.example.com.

$ nslookup www.example.com
Server:		127.0.0.53
Address:	127.0.0.53#53

Non-authoritative answer:
www.example.com	canonical name = example.com.
Name:	example.com
Address: 93.184.216.34

"Non-authoritative answer" = it came from a resolver (usually a cache), not the zone's own server. Both tools apply the resolv.conf search list (8.27), which dig does not - so host orders may work while dig orders says NXDOMAIN. That is not a DNS bug; the two tools asked for two different names.

What you can now do

Why it helps

dig is the tool you reach for in every DNS incident, and most misreadings come from skipping the header. During a migration, a TTL that keeps counting down tells you at once the answer is cached and the new record simply has not reached this resolver. SERVFAIL points you at the zone or its delegation, REFUSED at a box pointed at the wrong kind of server, a timeout at a firewall on port 53.

It also saves you from a quiet scripting bug: dig exits 0 for NXDOMAIN and SERVFAIL, so a health check like dig name && echo ok is always ok. And dig +trace gives you what the authoritative servers say right now, which is the evidence to hand to whoever owns the zone. These distinctions come up in SRE interviews as "what is the difference between NXDOMAIN and SERVFAIL".

Commands in this lesson

dig host nslookup

FAQ

What is the difference between NXDOMAIN and NOERROR with no answer?

NXDOMAIN means the name does not exist at all, for any record type. NOERROR with an empty ANSWER section (often called NODATA) means the name exists but has no record of the type you asked for, for example a name with an A record but no AAAA. Both carry an SOA in the AUTHORITY section, and both are cached as negative answers. Scripts that only check the status miss NODATA.

How do I know whether an answer came from a cache?

Query twice and watch the TTL. If it counts down, a resolver is serving a cached copy and telling you how long it will keep it. If it stays at the full value and the flags include aa, you are talking to the authoritative server. resolvectl query also prints Data from: cache or network. A query time of 0 to 1 ms is another hint of a cache hit.

Why does host or nslookup find a short name when dig does not?

host and nslookup apply the search domains from resolv.conf; dig does not unless you add +search. So with search prod.company.com in resolv.conf, host db tries db.prod.company.com and succeeds, while dig db asks for the literal name db. and gets NXDOMAIN. It is two tools asking two different names, not a DNS bug. Use full names with dig.

Why does dig +trace fail for my internal zone?

+trace starts at the public root servers and follows referrals from there. An internal zone like lab. or a private company domain is not delegated from the public root, so the root answers NXDOMAIN. For internal names, ask the authoritative internal server directly with dig @10.0.3.53 name, and compare with what your normal resolver returns.

Which dig output format should I use?

+short for scripts and quick checks, knowing it hides the TTL and the status (NXDOMAIN prints nothing). +noall +answer for reading: records with their TTLs, one per line, including CNAME chains. The full output when something is odd, because only it shows the status, the flags and the SERVER. Add +time=1 +tries=1 when probing servers that may not answer.

In an interview Junior

What is the difference between NXDOMAIN, SERVFAIL and REFUSED?

They are the status: in the header of dig output - the first thing to read:

Scripting trap: dig exits 0 for NXDOMAIN and SERVFAIL, so check the answer ([ -n "$(dig +short name)" ]), not the exit code.

Also asked: How can you tell from dig whether an answer came from a cache? · What does dig +trace show, and when do you use it? · What does the aa flag mean in dig output?

Practise this lesson in the terminal Free, in your browser - a real Ubuntu terminal to try it in, with missions that check your work.