dig - Asking DNS Directly
digsends 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 namepicks the resolver,+tracewalks root to TLD to authoritative yourself,+norecurseasks one server only what it already knows,+tcpforces TCP,+dnssecshows signatures.- Run
digtwice and watch the TTL fall: that is the resolver cache, visible. SERVFAILis a resolver failure, most often a broken DNSSEC chain or unreachable authoritative servers. It is not "the name does not exist" (that isNXDOMAIN).
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
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.
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.
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.
Read it top to bottom:
- status: NOERROR and ANSWER: 2 - it worked and there are two records. Check both:
NOERRORwithANSWER: 0means the name exists but has no record of that type. - flags: qr rd ra ad -
qrthis is a response,rdrecursion was requested,rathe server offers recursion,adthe resolver says it validated the data with DNSSEC. There is noaa, 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.8from 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.
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.
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.
| Status | What dig shows | First thing to check |
|---|---|---|
| NOERROR, ANSWER > 0 | The records. | Is the TTL and the SERVER what you expected? |
| NOERROR, ANSWER: 0 | Empty answer, SOA in the authority section (the NODATA shape, RFC 2308). | Wrong record type? Ask AAAA, MX, TXT explicitly. |
| NXDOMAIN | No answer, SOA in the authority section. | Typo, record not created, or a negative-cache entry from before you created it. |
| SERVFAIL | No answer, no authority. | Retry with +cd and +trace to separate DNSSEC failure from unreachable authoritative servers. |
| REFUSED | No 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):
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.
This is NOERROR with zero answers, so a script that only checks the status would call it success. Always look at the ANSWER count.
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
| Flag | Effect | When to use it |
|---|---|---|
@server | Ask this server instead of the one in your OS config. | Compare resolvers; test a new nameserver before the delegation changes. |
+short | Only the answer data. | Scripts and shell variables. |
+noall +answer | Only the ANSWER section, TTL kept. | Reading TTLs without the noise. |
+norecurse | Clear 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. |
+trace | dig walks root, TLD, authoritative itself. | Delegation problems, lame or missing NS records. Needs UDP 53 to the root servers. |
+tcp | Use TCP instead of UDP. | Truncated or large answers; checking that a server accepts TCP. |
+dnssec | Request DNSSEC records (RRSIG) and show them. | Signature and chain debugging. |
+cd | Set Checking Disabled. | Separate validation failure from a dead server. |
+nsid | Ask 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:
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
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):
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?":
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:
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 says | Production does | Why |
|---|---|---|
| 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 +traceanddig @<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
- 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).
digships with BIND, the reference implementation. - 2005 Spoofed answers are the problem; DNSSEC (RFC 4033 to 4035) adds signatures, which is why
+dnssec,+cdand theadflag 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 +tlsand+https; the macOSdigcaptured for this page, 9.10.6, answersInvalid option: +https. - 2019
ANYqueries 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.confshows. Check which one the app really uses before trusting adig. - 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
| System | Install | Notes |
|---|---|---|
| macOS | Already installed | dig -v here prints DiG 9.10.6, an old build without +https. |
| Debian, Ubuntu | sudo apt install dnsutils | Package name for dig and nslookup. |
| RHEL, Fedora | sudo dnf install bind-utils | |
| Alpine | apk add bind-tools | Common in containers, where dig is often missing. |
| Windows | Resolve-DnsName example.com | PowerShell cmdlet; nslookup also works. |
Everyday commands
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:
drill (from the ldns project) and kdig (from Knot DNS) are dig-compatible alternatives. A real drill reply prints rcode: where dig prints status::
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 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:
| OS | Lookup as the app sees it | Show resolver config |
|---|---|---|
| macOS | dscacheutil -q host -a name example.com | scutil --dns |
| Linux | getent hosts example.com (follows /etc/nsswitch.conf) | resolvectl status with systemd-resolved, else /etc/resolv.conf |
| Windows | Resolve-DnsName example.com | ipconfig /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:
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:
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.
| OS | Flush command | Note |
|---|---|---|
| macOS | sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder | Clears the OS cache only. |
| Linux (systemd-resolved) | sudo resolvectl flush-caches | Older systems: systemd-resolve --flush-caches. |
| Windows | ipconfig /flushdns | ipconfig /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).
| When | Action | Why |
|---|---|---|
| T minus 48 h | Lower 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 more | Confirm: 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. |
| T | Change the record to 198.51.100.20. | Every cache now holds at most a 300 s copy. |
| T plus 300 s | Verify 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 day | Raise the TTL back (for example 3600). | Restores cache benefit once you trust the change. |
Gotchas
Interview questions
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.
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 nameclears the RD bit; an authoritative server for the zone answers withaa, a TLD server returns a referral.
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 ANYprintedTruncated, retrying in TCP mode.
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.8and@1.1.1.1and your own.
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 hostson Linux,dscacheutil -q host -a nameon macOS. - Is a flush enough? Only for the OS cache; the browser and the app have their own.
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.
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
- The
.arpadomain holds reverse lookups:dig -x 1.1.1.1really asks for1.1.1.1.in-addr.arpaand, in the capture above, gotone.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.comprintedNSID: 61 74 6c 31 35 ("atl15")in a capture: the server naming the site that answered.- A real answer for
ANYonexample.comcontained a record with the textRFC8482, the server explaining why it will not list everything.
Cheat sheet
- 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
| Symptom | Command | Reads |
|---|---|---|
| Does it resolve at all? | dig name +noall +comments +answer | status and ANSWER count |
| Which resolver answered? | dig name (SERVER line) | The SERVER footer |
| Stale cache? | dig @8.8.8.8 name versus local | Different answers, falling TTL |
| Delegation broken? | dig +trace name | The tier whose Received line stops |
| SERVFAIL? | dig name +cd | Resolves with +cd means DNSSEC failed |
| Is the app seeing the same? | dscacheutil -q host -a name name or getent hosts name | Compare with dig |