Skip to main content

dig - Asking DNS Directly

TL;DR
  • dig sends one DNS query to a server you choose and prints the raw reply. It does not go through your OS resolver, so it skips /etc/hosts, the OS cache, search domains and mDNS. "dig works but the app fails" is expected, not a paradox.
  • Read the reply in this order: status (NOERROR, NXDOMAIN, SERVFAIL, REFUSED), then flags (qr rd ra aa ad tc), then the ANSWER count, then the TTL and the SERVER line.
  • dig @1.1.1.1 name picks the resolver, +trace walks root to TLD to authoritative yourself, +norecurse asks one server only what it already knows, +tcp forces TCP, +dnssec shows signatures.
  • Run dig twice and watch the TTL fall: that is the resolver cache, visible.
  • SERVFAIL is a resolver failure, most often a broken DNSSEC chain or unreachable authoritative servers. It is not "the name does not exist" (that is NXDOMAIN).

This is a tool page. The concepts it pokes at have one owner each: resolution, caching and TTL are on DNS resolution, record types, DNSSEC and encrypted DNS are on DNS records and routing, error strings are on error messages, and the layer-by-layer method is on debugging by layer. The animated walk of a lookup is the interactive on the DNS resolution page; this page does not repeat it.

Mental model​

ANALOGYEveryday

Your phone's contacts app is your receptionist: ask it for "Sam" and it checks your own address book first, then the office directory, then gives up. dig is calling directory assistance yourself: you pick which directory to ask, you hear exactly what the operator says, including how long they say the answer is good for, and your own address book is never consulted.

Where it breaks: directory assistance is one human. DNS is a chain: the operator you reach may itself have asked three others, or be reading from a note it wrote earlier. dig shows you the answer, and only the flags and TTL tell you whether it was fresh or remembered.

ANALOGYFor engineers

dig is a raw API call (curl against the DNS service); your application's getaddrinfo is the language runtime's cached client with its own config file, fallbacks and retries. Debugging with dig is like testing a backend with curl while the bug is in the SDK: the backend can be perfectly healthy and the SDK still wrong.

Where it breaks: a REST call is stateless, but the DNS server you hit has a cache, so two identical dig runs can differ. And unlike an SDK, the OS resolver is shared by every program on the machine, so its cache also affects programs you are not debugging.

How it works​

A lookup from your application takes a path dig mostly does not. The figure shows where dig enters and which flag exposes which hop.

Your machineRecursive resolver1. Root servers2. .com TLD servers3. Authoritativestub + OS cachehit: no packet sentdig skips this layerits own cachehit: TTL counts downdig @1.1.1.1 asks itrefers to .com NSrefers to example.com NSowns the answer (aa flag)dig +trace name : your dig does steps 1 to 3 itselfdig +norecurse @server name : one server, no walk, only what it knows
Your program reaches the stub and its cache. dig skips that layer and asks a recursive resolver directly (dig @server). +trace skips the recursive resolver as well and walks steps 1 to 3 itself. +norecurse asks one server only what it already knows.

Reading one reply​

This is a real capture (macOS, DiG 9.10.6, 14 Oct 2026), abridged: the global options and WHEN lines are removed. The figure labels each block.

dig example.com
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48869;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1; EDNS: version: 0, flags:; udp: 1232;example.com. IN Aexample.com. 260 IN A 104.20.23.154example.com. 260 IN A 172.66.147.243;; Query time: 6 msec;; SERVER: 192.168.0.1#53(192.168.0.1);; MSG SIZE rcvd: 72HEADER: status, flags, section countsOPT: EDNS, bigger UDP bufferQUESTION: name, class, type askedANSWER: name, TTL, class, type, dataFooter: query time, SERVER, size
One dig reply, block by block. The header tells you whether it worked, the answer tells you what and for how long, the footer tells you who answered and how fast. Real capture, abridged.

Read it top to bottom:

  • status: NOERROR and ANSWER: 2 - it worked and there are two records. Check both: NOERROR with ANSWER: 0 means the name exists but has no record of that type.
  • flags: qr rd ra ad - qr this is a response, rd recursion was requested, ra the server offers recursion, ad the resolver says it validated the data with DNSSEC. There is no aa, so this answer did not come straight from the zone's own server: it is cached or relayed. Flag meanings are in the table on DNS resolution.
  • 260 - the TTL in seconds still left in the resolver's cache, not the number the zone owner published.
  • SERVER: 192.168.0.1#53 - who answered. This is the first thing to check when two machines disagree.
  • Query time: 6 msec - answered from a nearby cache. The same name asked of 8.8.8.8 from this machine took 282 msec in a capture on the same day, so latency belongs to the path, not the name.

Watch a TTL fall​

The best beginner demo: ask twice, a few seconds apart.

dig example.com A +noall +answer; sleep 5; dig example.com A +noall +answer
example.com. 256 IN A 104.20.23.154
example.com. 256 IN A 172.66.147.243
example.com. 251 IN A 104.20.23.154
example.com. 251 IN A 172.66.147.243

Real capture, abridged (the DiG banner lines removed). 256 became 251 across the 5 second sleep: you are looking at the cache counting down. When it reaches 0 the resolver walks again and the TTL jumps back to what the zone publishes. Why the zone owner chooses that number, and the trade-off, is on DNS resolution.

Worked example: a record changed, one machine still sees the old one​

Documentation addresses (RFC 5737), constructed for illustration. app.example.test moved from 203.0.113.10 to 198.51.100.20, and a colleague still reaches the old server.

$ dig @8.8.8.8 app.example.test +short
198.51.100.20
$ dig @1.1.1.1 app.example.test +short
198.51.100.20
$ dig app.example.test +short # the machine's own resolver
203.0.113.10

Two public resolvers agree and the local one does not. Add +noall +answer to the local one and read the TTL: if it is a number counting down, that resolver cached the old record, and it will refresh when the TTL reaches 0 or when you flush it. If the local resolver is the same address for the whole company and still differs from public ones forever, suspect split-horizon (below) instead of caching.

Deep dive​

Status codes and the first thing to check​

The meanings are on DNS resolution; this table is the debugging side.

StatusWhat dig showsFirst thing to check
NOERROR, ANSWER > 0The records.Is the TTL and the SERVER what you expected?
NOERROR, ANSWER: 0Empty answer, SOA in the authority section (the NODATA shape, RFC 2308).Wrong record type? Ask AAAA, MX, TXT explicitly.
NXDOMAINNo answer, SOA in the authority section.Typo, record not created, or a negative-cache entry from before you created it.
SERVFAILNo answer, no authority.Retry with +cd and +trace to separate DNSSEC failure from unreachable authoritative servers.
REFUSEDNo answer.You asked a server that will not recurse for you or has an ACL against you. Pick another @server.

Three real captures from 14 Oct 2026 (macOS, DiG 9.10.6):

$ dig nosuchname-4fty-abc.com +noall +comments +authority
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 19162
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
com. 60 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1791962734 1800 900 604800 900

NXDOMAIN carries the zone's SOA. The last SOA field (900) is the MINIMUM that bounds the negative cache time (RFC 2308); the resolver here returned 60, lower than 900, so it evidently applies its own cap. If you query a name before creating it, expect this "does not exist" to linger for up to that long.

$ dig nosuchname-4fty.example.com +noall +comments +authority
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33578
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
example.com. 60 IN SOA elliott.ns.cloudflare.com. dns.cloudflare.com. 2416374680 10000 2400 604800 1800

This is NOERROR with zero answers, so a script that only checks the status would call it success. Always look at the ANSWER count.

$ dig dnssec-failed.org +noall +comments
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 20791
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
 
$ dig dnssec-failed.org +cd +short
96.99.227.255

dnssec-failed.org is a test domain that is deliberately published with bad DNSSEC data. A validating resolver returns SERVFAIL, but with +cd (Checking Disabled, the CD bit from RFC 4035) the same resolver hands over the address. SERVFAIL that disappears under +cd is a DNSSEC validation failure; SERVFAIL that stays is usually unreachable or broken authoritative servers. Note the SERVFAIL reply has no ad flag and the NOERROR one above does. Newer dig versions also print an Extended DNS Error when the resolver supplies one (RFC 8914), which names the cause directly; the 9.10.6 here does not.

Flags worth knowing​

FlagEffectWhen to use it
@serverAsk this server instead of the one in your OS config.Compare resolvers; test a new nameserver before the delegation changes.
+shortOnly the answer data.Scripts and shell variables.
+noall +answerOnly the ANSWER section, TTL kept.Reading TTLs without the noise.
+norecurseClear the RD bit: the server answers only from what it knows.Is it in this resolver's cache right now? Ask an authoritative or TLD server for a referral.
+tracedig walks root, TLD, authoritative itself.Delegation problems, lame or missing NS records. Needs UDP 53 to the root servers.
+tcpUse TCP instead of UDP.Truncated or large answers; checking that a server accepts TCP.
+dnssecRequest DNSSEC records (RRSIG) and show them.Signature and chain debugging.
+cdSet Checking Disabled.Separate validation failure from a dead server.
+nsidAsk the server to name itself (RFC 5001).Which anycast site answered?
+subnet=Send an EDNS Client Subnet (RFC 7871).See geo-steered answers as another region. The macOS 9.10.6 dig accepts it and echoes CLIENT-SUBNET: 198.51.100.0/24/0 (scope 0, so that resolver did not tailor the reply).

TC bit, +tcp and why ANY is gone​

UDP is the default; a reply that does not fit is sent with the TC flag and the client retries over TCP (RFC 1035, the table on DNS resolution). dig does the retry for you and says so. A real capture:

$ dig example.com ANY +noall +comments +answer
;; Truncated, retrying in TCP mode.
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 22, AUTHORITY: 0, ADDITIONAL: 1
...
example.com. 712 IN HINFO "RFC8482" ""

Two lessons from one capture. The truncation and TCP retry happened without you asking. And ANY is deprecated: RFC 8482 lets servers answer with a minimal set instead of everything, and some do by returning an HINFO record whose text is RFC8482, which is the one line above (the other 21 answers came from this particular resolver and server, and may differ for you). Do not use ANY to "list all records"; ask for each type (A, AAAA, MX, TXT, NS, SOA) explicitly. Why DNS is UDP-first and when it needs TCP is on UDP and DNS resolution.

Reading a CNAME chain​

$ dig www.github.com +noall +answer
www.github.com. 30 IN CNAME github.com.
github.com. 33 IN A 140.82.114.4

Real capture. Read it as: the name you asked for is an alias; the answer is whatever the target resolves to. Each line has its own TTL (30 and 33 here), and the answer is good only until the shortest one expires.

+trace versus +norecurse​

+trace does the walk yourself, ignoring every cache, so it shows what the world's authoritative servers say right now. A real trace, abridged: the 13 root NS lines, the 13 com. NS lines and the RRSIG lines are removed, and +trace output in full also shows the DNSSEC chain (DS and RRSIG records):

$ dig +trace example.com
. 9231 IN NS l.root-servers.net. (13 lines)
;; Received 1097 bytes from 192.168.0.1#53 in 4 ms
com. 172800 IN NS l.gtld-servers.net. (13 lines)
com. 86400 IN DS 19718 13 2 8ACBB0CD...
;; Received 1171 bytes from 198.41.0.4#53(a.root-servers.net) in 19 ms
example.com. 172800 IN NS hera.ns.cloudflare.com.
example.com. 172800 IN NS elliott.ns.cloudflare.com.
;; Received 506 bytes from 192.43.172.30#53(i.gtld-servers.net) in 52 ms
example.com. 300 IN A 104.20.23.154
example.com. 300 IN A 172.66.147.243
;; Received 179 bytes from 172.64.32.162#53(hera.ns.cloudflare.com) in 58 ms

Each Received line names which server gave that tier and how long it took: that is how you find the slow or dead hop. Compare the final TTL here (300, what the zone publishes) with the 260 your resolver showed earlier. A plain dig on a shared resolver can differ from +trace for as long as the cache holds, which is exactly the point.

+norecurse is the opposite tool. Against your own resolver it asks "is this in your cache?":

$ dig +norecurse example.com +noall +comments +answer
;; flags: qr ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
example.com. 243 IN A 104.20.23.154
...

Note the flags have no rd, because you cleared it. An answer means the resolver already had it. Against a TLD server it returns a referral instead of an answer:

$ dig +norecurse @a.gtld-servers.net example.com NS +noall +comments +authority
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 13
example.com. 172800 IN NS hera.ns.cloudflare.com.
example.com. 172800 IN NS elliott.ns.cloudflare.com.

That is a referral: the answer section is empty and the NS records sit in AUTHORITY (the 13 ADDITIONAL records are address records for those nameservers). No aa and no ra: this server is not the owner of example.com and will not recurse.

Split-horizon DNS​

Some zones deliberately answer differently depending on who asks: an internal view returns private addresses, the public view returns public ones. Debug it by asking each view directly: dig @<internal-resolver> name from inside, dig @<public-resolver> name from outside, and compare. If they differ, check whether the difference is configured (it is a feature) before treating it as a cache bug. This is the case where "which resolver answered" matters more than any other field.

Textbook vs real world​

The textbook saysProduction doesWhy
DNS is instant.A cached lookup took 6 msec and a lookup to a far public resolver took 282 msec in real captures on this page. A resolver that does not answer can stall an application for seconds: the Linux resolver defaults to a 5 second timeout and 2 attempts per server (resolv.conf(5)).Latency is the cache hit rate and the path, and the failure case is a timeout, not an error.
A TTL is honoured everywhere, so a low TTL means an instant change.Resolvers may clamp TTLs, and applications add their own cache. A JVM caches lookups per its networkaddress.cache.ttl setting, which can keep an address far longer than the DNS TTL (Java networking properties).Every layer between the zone and the code can hold its own copy.
There is one DNS answer for a name.Split-horizon views, geo-steering and EDNS Client Subnet give different answers to different askers (RFC 7871).The right answer depends on who asks, so you must pin the resolver when comparing.

In production​

  • Zero-downtime cutover. Lower the record's TTL, wait at least the old TTL so every cache is now holding the short one, switch the record, then raise the TTL again. The timeline for a 24 hour old TTL is under Hands-on, and the record-level recipe is on DNS records and routing.
  • Health-checked records. AWS Route 53 can stop returning a record when its health check fails, which is DNS-level failover bounded by the record's TTL (Route 53 failover). Verify with dig @<each-nameserver> and the TTL.
  • Resolver dependence. On 21 October 2016 a DDoS against the managed DNS provider Dyn made many popular sites unreachable for users whose resolvers could not get an answer from Dyn's nameservers, even though the sites' own servers were up (Dyn's analysis, archived). dig +trace and dig @<each-ns> are how you tell "the provider's nameservers are down" from "my resolver is down", and why many zones list nameservers at more than one provider.
  • Public resolvers for comparison. 1.1.1.1 (Cloudflare), 8.8.8.8 (Google Public DNS), 9.9.9.9 (Quad9). They are the standard "third opinion" when the local resolver disagrees. Use their answers as a control, never as proof of what your users see.

History and evolution​

HISTORY
  • 1983 HOSTS.TXT, one file every host had to copy, stops scaling; RFC 882 and 883 define a distributed, hierarchical DNS (RFC 882, RFC 883).
  • 1987 RFC 1034 and 1035 replace them and are still the base specification (RFC 1035). dig ships with BIND, the reference implementation.
  • 2005 Spoofed answers are the problem; DNSSEC (RFC 4033 to 4035) adds signatures, which is why +dnssec, +cd and the ad flag exist (RFC 4035).
  • 2016 Plain-text DNS leaks every query; DNS over TLS on port 853 (RFC 7858), then DNS over HTTPS in 2018 (RFC 8484). Newer BIND releases (the 9.18 series) add dig +tls and +https; the macOS dig captured for this page, 9.10.6, answers Invalid option: +https.
  • 2019 ANY queries were an amplification vector and an expensive one for servers; RFC 8482 lets servers answer minimally (RFC 8482).
  • 2020 Failures like SERVFAIL gave no cause; Extended DNS Errors attach a reason code (RFC 8914).
  • Now Browsers and OSes increasingly use encrypted DNS to a configured resolver, which can bypass the resolver your /etc/resolv.conf shows. Check which one the app really uses before trusting a dig.
  • Next Encrypted transports becoming the default between stub and resolver, with dig and its alternatives (drill, kdig) gaining matching flags.

Hands-on​

All output below was captured on macOS on 14 Oct 2026 (DiG 9.10.6) unless labelled illustrative. It is abridged, and answers, TTLs and addresses will differ when you run it.

Install​

SystemInstallNotes
macOSAlready installeddig -v here prints DiG 9.10.6, an old build without +https.
Debian, Ubuntusudo apt install dnsutilsPackage name for dig and nslookup.
RHEL, Fedorasudo dnf install bind-utils
Alpineapk add bind-toolsCommon in containers, where dig is often missing.
WindowsResolve-DnsName example.comPowerShell cmdlet; nslookup also works.

Everyday commands​

dig example.com A +noall +answer
dig @1.1.1.1 example.com +short
dig gmail.com MX +noall +answer
dig example.com NS +noall +answer
dig example.com SOA +noall +answer
dig -x 1.1.1.1 +short
example.com. 260 IN A 104.20.23.154
example.com. 260 IN A 172.66.147.243
 
172.66.147.243
104.20.23.154
 
gmail.com. 30 IN MX 5 gmail-smtp-in.l.google.com.
gmail.com. 30 IN MX 10 alt1.gmail-smtp-in.l.google.com.
(three more MX lines)
 
example.com. 40264 IN NS elliott.ns.cloudflare.com.
example.com. 40264 IN NS hera.ns.cloudflare.com.
 
example.com. 1798 IN SOA elliott.ns.cloudflare.com. dns.cloudflare.com. 2416374680 10000 2400 604800 1800
 
one.one.one.one.

Output blocks are shown in the order of the commands, with the DiG banner lines removed. In the MX lines the number before the host is the preference: lower is tried first. The -x query is a PTR lookup, asked of the name 1.1.1.1.in-addr.arpa.

Other clients​

nslookup (installed on macOS and Windows) is older and prints less:

$ nslookup example.com
Server: 192.168.0.1
Address: 192.168.0.1#53
 
Non-authoritative answer:
Name: example.com
Address: 104.20.23.154

drill (from the ldns project) and kdig (from Knot DNS) are dig-compatible alternatives. A real drill reply prints rcode: where dig prints status::

$ drill example.com
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 57077
;; flags: qr rd ra ; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0

dig vs the OS resolver: the capture that explains it​

dig asks a DNS server. Your applications call the OS resolver, which also consults /etc/hosts and, on macOS, mDNS. Same name, two answers:

dig localhost +noall +comments
dscacheutil -q host -a name localhost
ping -c1 localhost
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 61207
name: localhost
ipv6_address: ::1
name: localhost
ip_address: 127.0.0.1
PING localhost (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.067 ms

dig says the name does not exist, while ping and the OS lookup (dscacheutil) find it, because /etc/hosts has 127.0.0.1 localhost and DNS never sees it. That is the whole "dig works but the app fails" gotcha in mirror image: here the app works and dig fails. Use the OS-level lookup when you want what the app sees:

OSLookup as the app sees itShow resolver config
macOSdscacheutil -q host -a name example.comscutil --dns
Linuxgetent hosts example.com (follows /etc/nsswitch.conf)resolvectl status with systemd-resolved, else /etc/resolv.conf
WindowsResolve-DnsName example.comipconfig /all

On macOS there is no /etc/nsswitch.conf (a real grep for it reported "No such file or directory"); the order is built into the system resolver. scutil --dns shows the resolvers, and a real capture shows why .local names behave differently from dig:

$ scutil --dns
resolver #1
nameserver[0] : 192.168.0.1
if_index : 15 (en0)
resolver #2
domain : local
options : mdns
timeout : 5

Resolver #1 is the one dig used (the SERVER line above says 192.168.0.1). Resolver #2 sends .local names to mDNS instead, and dig never does that. The Linux equivalent, illustrative and not captured on this machine:

$ resolvectl status
Link 2 (eth0)
Current DNS Server: 192.0.2.53

Under systemd-resolved, /etc/resolv.conf often points at the local stub 127.0.0.53, so dig with no @server asks the stub, not your upstream. The line to read is Current DNS Server.

Flush the cache​

These were not run here: they need sudo and change the machine.

OSFlush commandNote
macOSsudo dscacheutil -flushcache; sudo killall -HUP mDNSResponderClears the OS cache only.
Linux (systemd-resolved)sudo resolvectl flush-cachesOlder systems: systemd-resolve --flush-caches.
Windowsipconfig /flushdnsipconfig /displaydns lists the cache.

Flushing the OS cache does not touch the browser's cache, an application's own cache (the JVM), or the recursive resolver's cache. If a flush "did nothing", the stale copy is in a layer you did not flush: ask the resolver directly with dig @resolver name and read the TTL.

Worked cutover timeline​

Documentation addresses (RFC 5737), constructed arithmetic: move app.example.test from 203.0.113.10 to 198.51.100.20 with an old TTL of 86400 seconds (24 hours).

WhenActionWhy
T minus 48 hLower the TTL to 300 s on the existing record.Caches that fetched the record before now still hold up to 86400 s.
T minus 24 h or moreConfirm: dig @<each-ns> app.example.test +noall +answer shows TTL 300.After one old TTL has elapsed no cache can still hold the 24 hour copy.
TChange the record to 198.51.100.20.Every cache now holds at most a 300 s copy.
T plus 300 sVerify with dig @8.8.8.8 and dig @1.1.1.1.Worst-case staleness is the short TTL, here 5 minutes.
T plus a dayRaise the TTL back (for example 3600).Restores cache benefit once you trust the change.

Gotchas​

GOTCHAS5
dig works, so the application must be fine
SERVFAIL means the DNS server is down
A low TTL means the change is instant everywhere
dig ANY lists everything about a name
NOERROR means I got an answer

Interview questions​

INTERVIEWWhat is the difference between NXDOMAIN and SERVFAIL?junior
Show answer

NXDOMAIN is an answer: the authoritative data says the name does not exist. SERVFAIL is a failure: the resolver could not get a usable answer, for example because the authoritative servers were unreachable or DNSSEC validation failed. The first is about the name, the second is about the resolution.

Follow-ups:

  • What is negative caching? Resolvers cache the "does not exist" answer for a time derived from the SOA (the smaller of its TTL and MINIMUM, RFC 2308), so a name created afterwards may keep returning NXDOMAIN for a while.
  • How do you tell the SERVFAIL causes apart? Query with +cd: if it then resolves, DNSSEC validation failed.
INTERVIEWWhat is the difference between a recursive resolver and an authoritative server?junior
Show answer

A recursive resolver walks the tree on your behalf and caches the result; an authoritative server owns a zone and answers from its own data. In dig, ra in the reply means the server offers recursion, and aa means it is authoritative for the answer.

Follow-ups:

  • How do you ask one without recursion? dig +norecurse @server name clears the RD bit; an authoritative server for the zone answers with aa, a TLD server returns a referral.
INTERVIEWWhy is DNS mostly UDP, and when does it use TCP?mid
Show answer

A lookup is one small question and one small answer, so UDP avoids the handshake. DNS switches to TCP when the answer is truncated (the TC flag), for zone transfers, and for large DNSSEC responses; DoT also runs over TCP (port 853). TCP support is required, not optional (RFC 7766).

Follow-ups:

  • How do you test it? dig +tcp name. A server that refuses TCP will fail on large answers while small ones work.
  • How did we see truncation? dig example.com ANY printed Truncated, retrying in TCP mode.
INTERVIEWHow would you run a zero-downtime DNS cutover?mid
Show answer

Lower the TTL well ahead, wait at least the old TTL so no cache holds a long copy, switch the record, verify against several resolvers, then raise the TTL again. The worst-case staleness after the switch is the short TTL.

Follow-ups:

  • How long before? At least the old TTL. For 86400 s that is 24 hours.
  • Why check several resolvers? Some clamp TTLs; compare @8.8.8.8 and @1.1.1.1 and your own.
INTERVIEWdig resolves the name but my application cannot. What do you check?mid
Show answer

dig bypasses the OS resolver. Check /etc/hosts, the order in /etc/nsswitch.conf (Linux), search domains in /etc/resolv.conf, mDNS for .local, and an application-level cache such as the JVM's networkaddress.cache.ttl. Compare getent hosts name or dscacheutil -q host -a name name with dig.

Follow-ups:

  • Which command shows what the app resolves? getent hosts on Linux, dscacheutil -q host -a name on macOS.
  • Is a flush enough? Only for the OS cache; the browser and the app have their own.
INTERVIEWWhat are the trade-offs of a low versus a high TTL?senior
Show answer

A low TTL gives agility (fast failover, quick cutover) at the cost of more queries to your authoritative servers and more lookup latency for clients. A high TTL gives cache hits and resilience to a short authoritative outage but makes a bad record slow to fix. Pick per record: low for things you may move, high for stable ones.

Follow-ups:

  • What if the authoritative servers go down? Records with long TTLs keep working from cache until they expire; short ones fail sooner.
  • Can you rely on the TTL? No: resolvers can clamp and apps can cache longer.
INTERVIEWHow do you debug a split-horizon problem?senior
Show answer

Ask each view directly: dig @internal-resolver name from inside and dig @public-resolver name from outside, then compare answers and TTLs. If the difference is the configured one, it is by design; if the internal view is missing a record that the public one has, the internal zone needs updating. Always pin the resolver with @, because the system default changes between networks, VPNs included.

Follow-ups:

  • Why does the VPN matter? Connecting changes which resolver your OS uses, and so which view you get.
  • How do you tell split-horizon from a stale cache? A stale cache converges when the TTL expires; a split-horizon difference is permanent.

Fun facts​

FACTSTrivia worth knowing
  • The .arpa domain holds reverse lookups: dig -x 1.1.1.1 really asks for 1.1.1.1.in-addr.arpa and, in the capture above, got one.one.one.one..
  • The root zone has 13 server letters (the trace above lists a to m) but each letter is many machines reached by anycast; the live count is on root-servers.org.
  • dig +nsid @1.1.1.1 example.com printed NSID: 61 74 6c 31 35 ("atl15") in a capture: the server naming the site that answered.
  • A real answer for ANY on example.com contained a record with the text RFC8482, the server explaining why it will not list everything.

Cheat sheet​

NUMBERSNumbers to remember
  • 53 - the DNS port, UDP and TCP; 853 - DNS over TLS
  • 5 s, 2 attempts - the Linux resolver default timeout and retries per server, from resolv.conf(5)
  • 0 TTL left - the cache walks again; the TTL then jumps back to the published value
  • 13 - root server letters; 512 bytes - the original UDP limit, 1232 - the EDNS buffer seen in these captures
SymptomCommandReads
Does it resolve at all?dig name +noall +comments +answerstatus and ANSWER count
Which resolver answered?dig name (SERVER line)The SERVER footer
Stale cache?dig @8.8.8.8 name versus localDifferent answers, falling TTL
Delegation broken?dig +trace nameThe tier whose Received line stops
SERVFAIL?dig name +cdResolves with +cd means DNSSEC failed
Is the app seeing the same?dscacheutil -q host -a name name or getent hosts nameCompare with dig