is it dns? / wiki ← back to the live monitor

🧬 Anatomy of a DNS query

What actually happens between you typing example.com in your browser and the bytes arriving. DNS has many components, each providing a different role in the protocol. See query-types for the record types that flow through this machinery, and nslookup-and-dig for the tools to inspect it.

The four boxes#

+-----------+    +------------------+    +------------------+    +------------------+
| Stub      |    | Recursive        |    | Root / TLD /     |    | Authoritative    |
| resolver  |--->| resolver         |--->| delegation chain |--->| for the zone     |
| (your app |    | (1.1.1.1,        |    |                  |    | (your DNS host:  |
|  / your   |    |  8.8.8.8,        |    |                  |    |  Cloudflare,     |
|  OS)      |    |  your ISP's,     |    |                  |    |  a DNS server    |
|           |    |  your router's,  |    |                  |    |  you run,        |
|           |    |  systemd-        |    |                  |    |  a commercial    |
|           |    |  resolved)       |    |                  |    |  DNS appliance)  |
+-----------+    +------------------+    +------------------+    +------------------+

Stub resolver#

The library inside your application or operating system. It does not know the recursive process: it just 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), serving the roughly 1,440 TLDs in the root zone. They do not answer "what is the A record for x": they tell the recursive where to ask next. Each step is a delegation.

.                                   (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, it returns REFUSED (rcode 5). Some older implementations returned an "upward referral" to the root instead; current mainstream authoritative software answers REFUSED.

Note: nothing in the protocol stops one server from doing both jobs (authoritative for its zones, recursive for its clients), and some enterprise resolvers are configured that way. The large public DNS hosts keep the roles on separate infrastructure: Cloudflare's authoritative nameservers, for example, answer only the zones they host and return REFUSED for everything else, while its recursive service lives on 1.1.1.1.

Example: isitdns.net is served by:

  • coleman.ns.cloudflare.com, hera.ns.cloudflare.com (its registered authoritative nameservers, visible in its NS records)

A full query walked through#

Goal: resolve example.isitdns.net from cold cache.

  1. Stub → recursive: QUERY A example.isitdns.net, +rd
  2. Recursive checks cache: miss.
  3. Recursive → root (.): "who serves net.?" Answer: a referral with the .net NS list (a.gtld-servers.net. etc.) and glue.
  4. Recursive → .net: "who serves isitdns.net.?" Answer: a referral with coleman.ns.cloudflare.com. / hera.ns.cloudflare.com.. Since these NS names live outside isitdns.net (out-of-bailiwick), no glue is needed in the referral; the resolver looks up their addresses through normal recursion in cloudflare.com. The harder case is in-bailiwick NS, where the parent must supply glue A/AAAA alongside the NS to break a chicken-and-egg lookup.
  5. Recursive → coleman.ns.cloudflare.com: "A example.isitdns.net?" Answer: 192.0.2.1, TTL 300, with the aa (Authoritative Answer) bit set and no ra: this server owns the zone, answers from its own data, and does not recurse.
  6. Recursive → stub: 192.0.2.1, TTL 300, no aa bit (recursive is not authoritative: it is relaying). The ad flag is set when the resolver has validated the DNSSEC chain and the client's query carried the ad or do (DNSSEC OK) bit (RFC 6840 §5.8); on-path interceptors can strip it before it reaches the stub.
  7. Stub returns to the application. Done.

The next query for the same name within 300 seconds short-circuits at step 2.

Watch it happen#

The full delegation walk#

+trace bypasses the recursive cache and contacts each level of the hierarchy directly. It shows the referral chain, the glue records, the delegation hand-off, and the final authoritative answer.

dig +trace example.isitdns.net

nslookup cannot trace the delegation chain. Use dig.

+trace uses your local stub only for the initial lookup of the root NS list, then talks directly to authoritative servers at each level. If your network intercepts DNS on port 53, +trace can lie. Run it from a known-clean path (or over a VPN). Recent versions of dig also turn on +dnssec automatically during +trace, so DNSKEY lookups and DS/RRSIG records appear at each level; use dig +trace +nodnssec to suppress them.

A plain query (the everyday case)#

Most of the time you want to watch a single recursive query and read its flags. Ask a public validating resolver directly so the local stub does not interfere:

dig @1.1.1.1 example.isitdns.net A
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27823
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232

;; QUESTION SECTION:
;example.isitdns.net.           IN  A

;; ANSWER SECTION:
example.isitdns.net.    300 IN  A   192.0.2.1

;; Query time: 3 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)

The flags line shows qr (this is a response), rd (client asked for recursion), ra (server will recurse), and ad (the resolver validated the zone's DNSSEC chain; modern dig requests this bit by default, and it only appears for signed zones on validating resolvers, see below). The OPT pseudosection confirms EDNS0 is in use (see EDNS0 section below).

nslookup example.isitdns.net 1.1.1.1
Server:         1.1.1.1
Address:        1.1.1.1#53

Non-authoritative answer:
Name:   example.isitdns.net
Address: 192.0.2.1

Windows (PowerShell): the command below performs the same lookup using the OS resolver.

Resolve-DnsName example.isitdns.net

Where queries go wrong#

Failure stageWhat you'll seeLinked reading
Stub couldn't reach the recursiveSERVFAIL, timeouts, "no servers reachable"when-its-dns
Recursive couldn't reach a parentpartial +trace, then SERVFAILwhen-its-dns §5 (glue)
Authoritative returned NXDOMAINNXDOMAIN even after fixing itwhen-its-dns §2 (negative caching)
Authoritative returned NOERROR + zero answersAn app that "can't find" a name that existswhen-its-dns §6
DNSSEC chain brokeSERVFAIL on validating resolvers, success on +cddnssec · dnssec-troubleshooting
Response too big for UDPSilent partial answerswhen-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 mismatchName resolves differently depending on networksplit-horizon

The flags every dig answer carries#

The header line in every dig response looks like this:

;; flags: qr rd ra ad ...
FlagSet byMeaning
qrserverThis is a response, not a query
rdclient"Recursion Desired": client asks the server to recurse
raserver"Recursion Available": server is willing
aaserver"Authoritative Answer": set by an authoritative server answering for a zone it owns; absent from recursive responses (the recursive is relaying, not authoritative)
tcserver"Truncated": response exceeded the negotiated buffer (512 bytes without EDNS0, or the advertised EDNS0 size); client must retry over TCP
advalidator"Authenticated Data": resolver validated the [[dnssec
cdclient"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 the ad bit when the client requested it via the ad or do query bit. Recent versions of dig set the ad bit on queries by default, so the flag shows up on a plain dig against a validating resolver (as in the example above); nslookup and older tools never request it. curl's DoH JSON API always reports the resolver's validation result:

curl -s -H 'accept: application/dns-json' \
  'https://cloudflare-dns.com/dns-query?name=isitdns.net&type=A'
{
  "Status": 0,
  "TC": false,
  "RD": true,
  "RA": true,
  "AD": true,
  "CD": false,
  ...
  "Answer": [
    { "name": "isitdns.net", "type": 1, "TTL": 300, "data": "104.21.56.211" },
    { "name": "isitdns.net", "type": 1, "TTL": 300, "data": "172.67.155.242" }
  ]
}

"AD": true means Cloudflare's resolver validated the DNSSEC chain for isitdns.net end to end. Compare with dnssec-failed.org, which has a broken 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: 1

SERVFAIL with no answer is the signature of a broken DNSSEC chain on a validating resolver. Modern resolvers also attach an Extended DNS Error (RFC 8914) to the response's OPT section saying why (Quad9 returns EDE: 6 (DNSSEC Bogus) for this zone; the exact code depends on how the chain is broken, and other resolvers report it differently: Google's DoH flags the same zone as EDE 9 (DNSKEY Missing)). See dnssec for how the chain works.

nslookup cannot show the ad, aa, tc, or cd bits. Use dig or the DoH API above.

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. That means the udp: value is the buffer size the server advertises (the largest UDP payload it is willing to accept), not the size your client sent. The client advertises its own buffer size in the query's OPT record; dig defaults to 1232 and you can change it with +bufsize. Each side simply states its own limit, and the sender of any given message respects the other side's.

Without +dnssec the do flag is absent:

dig @1.1.1.1 example.isitdns.net A
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232

Add +dnssec and the client sets the do bit in its query, advertising that it can handle DNSSEC records. The server echoes do back and includes signatures in the answer. Note the udp: value does not change: 1.1.1.1 advertises 1232 either way, because that is the server's own limit and it has nothing to do with the do bit:

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==

Your RRSIG will not match this one byte for byte: the two timestamps (signature expiration and inception) and the signature itself change every time Cloudflare re-signs the zone. The stable parts are the covered type (A), algorithm (13, ECDSA P-256), label count (3), and original TTL (300).

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.

nslookup cannot show the EDNS OPT section, the do flag, or RRSIG records. Use dig.


See also: dig-along · dig-flags · nslookup-and-dig · DNSSEC · dnssec-troubleshooting · delegations · query-types · when-its-dns