🧬 Anatomy of a DNS query
Asks one resolver for one address, and shows every part of the answer.
dig @1.1.1.1 example.isitdns.net AGO NOERROR, one A record, and a Query time in milliseconds
NO GO no reply at all
The four boxes#
Stub resolver#
The library inside your application or operating system. It does not know the recursive process: it asks "give me the A record for example.com" of the configured resolver (usually /etc/resolv.conf on Unix, the per-adapter DNS settings on Windows, or a system-managed resolver on macOS).
The stub sets the RD (Recursion Desired) flag on its query: that is the bit that says "do the work for me."
Recursive resolver#
The hard worker. It accepts a stub's query and produces a final answer by chasing referrals through the delegation chain. Holds a cache, validates DNSSEC (or not), enforces policies (RPZ, ad-blocking, etc.).
Examples in your daily life:
1.1.1.1(Cloudflare) ·8.8.8.8(Google) ·9.9.9.9(Quad9)- The DNS server your ISP gives you
- The resolver baked into your home router or firewall (which usually forwards upstream)
systemd-resolved/mDNSResponder(these are stub + cache, not full recursive: they forward upstream)
Root / TLD / delegation chain#
13 root server letters (a.root-servers.net through m.root-servers.net, IPv4 + IPv6 each; IANA root servers list) serve the root zone. They do not answer "what is the A record for x": each one tells the recursive where to ask next. Each hand-off is a delegation, and the parent includes glue (the child NS's own address) only when the NS name lives inside the zone being delegated; see delegations for the exact rule.
. (root)
net. (TLD)
isitdns.net. (zone)
example.isitdns.net. (record)Authoritative server#
The server that holds the zone's actual records. It answers only the names it is authoritative for. A dedicated authoritative-only server does not recurse: if you ask it about a name outside its zones, BIND returns REFUSED (RFC 1035 §4.1.1, RCODE 5).
Nothing in the protocol stops one server doing both jobs, but large public hosts keep them separate: Cloudflare's authoritative nameservers answer only the zones they host and return REFUSED for the rest, while its recursive service is 1.1.1.1. isitdns.net is served by coleman.ns.cloudflare.com and hera.ns.cloudflare.com (NS records).
The recursive resolver relays the final answer to the stub with aa cleared (it is not authoritative) and ad set only if the query asked for it and the chain validated; see below.
Where queries go wrong#
| Failure stage | What you'll see | Linked reading |
|---|---|---|
| Stub couldn't reach the recursive | SERVFAIL, timeouts, "no servers reachable" | when-its-dns |
| Recursive couldn't reach a parent | partial +trace, then SERVFAIL | when-its-dns §5 (glue) |
Authoritative returned NXDOMAIN | NXDOMAIN even after fixing it | when-its-dns §2 (negative caching) |
Authoritative returned NOERROR + zero answers | An app that "can't find" a name that exists | when-its-dns §6 |
| DNSSEC chain broke | SERVFAIL on validating resolvers, success on +cd | dnssec · dnssec-troubleshooting |
| Response too big for UDP | Only big answers fail | when-its-dns §7 |
| Resolver is intercepted (captive portal, router-level DNS rewrite) | +trace works, +short doesn't (if port 53 itself is intercepted, +trace lies too) | when-its-dns §9 |
| Split-horizon mismatch | Name resolves differently depending on network | split-horizon |
The flags every dig answer carries#
The header line in every dig response looks like this:
;; flags: qr rd ra ad ...| Flag | Set by | Meaning |
|---|---|---|
qr | server | This is a response, not a query |
rd | client | "Recursion Desired": client asks the server to recurse |
ra | server | "Recursion Available": server is willing |
aa | server | "Authoritative Answer": set by an authoritative server answering for a zone it owns; absent from recursive responses (the recursive is relaying, not authoritative) |
tc | server | "Truncated": response exceeded the negotiated buffer (512 bytes without EDNS0, or the advertised EDNS0 size); client must retry over TCP |
ad | validator | "Authenticated Data": resolver validated the DNSSEC chain (RFC 6840 §5.8: only returned when the query carried the ad or do bit) |
cd | client | "Checking Disabled": client asks the validator to skip DNSSEC checking |
See dig-flags for the full flag reference and how to read each section of a dig response.
Seeing the ad flag live. A validating resolver only returns ad when the query carried the ad or do bit (RFC 6840 §5.8). dig sets that bit on every query by default (BIND 9 dig(1), +adflag), which is why the block at the top of this page already shows ad against a validating resolver. The DoH JSON API reports the same result as "AD": true.
Compare with dnssec-failed.org, a domain with a broken DNSSEC chain:
dig @9.9.9.9 dnssec-failed.org A;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 48029
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1SERVFAIL with no answer and no ad bit is what a broken chain looks like on a validating resolver. Modern resolvers also attach an Extended DNS Error (RFC 8914) to the OPT section naming what the validator tripped over. See dnssec for how the chain works.
EDNS0: the upgrade pack#
EDNS0 (RFC 6891) added bigger UDP payloads, the DNSSEC OK (do) bit, and an extended rcode space. Client-subnet (ECS) is a later EDNS0 option defined separately in RFC 7871. Modern resolvers use EDNS0; ancient firewalls sometimes drop EDNS0 queries and force fallback. See when-its-dns §7 for the symptoms.
The OPT pseudosection in dig output is the OPT record from the server's response. The udp: value is the buffer size the server advertises (the largest UDP payload it will accept), not the size the client sent; the client advertises its own in the query's OPT record, dig defaults to 1232, and +bufsize changes it. Each side states its own limit.
The block at the top of this page carries no do flag: no +dnssec, no signatures. Add +dnssec and the client sets do in its query; the server echoes it back and includes signatures in the answer. The udp: value does not change, because that is the server's own limit and has nothing to do with do:
dig @1.1.1.1 example.isitdns.net A +dnssec;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; ANSWER SECTION:
example.isitdns.net. 300 IN A 192.0.2.1
example.isitdns.net. 300 IN RRSIG A 13 3 300 20260718034843 20260716014843 34505 isitdns.net. 0vOiCtg3zo9baBNY+k4IXjoVKeu98HGbLVyEvdsEypgWYFEG05Et1xAx g2cbq6BxvmE5oyAxspsBGYbESMPcPQ==The timestamps and the signature change every time the zone is re-signed. The covered type (
A), algorithm (13, ECDSA P-256), label count (3), and original TTL (300) stay stable.
1232 bytes is the safe-transport size recommended after DNS Flag Day 2020 to avoid IP fragmentation; most modern resolvers and clients advertise it.
The RRSIG record is the DNSSEC signature over the A RRset. See dnssec for how a resolver uses it to validate the answer.