Mental Model · not the docs

DNS, Under the Hood

Recall reference for engineers. Not "phone book for the internet" — the actual resolution chain, caching layers, record types, transport, and the gotchas that cause 3am incidents.

TL;DR

Your app calls getaddrinfo() → the OS stub resolver asks a recursive resolver (ISP/1.1.1.1/8.8.8.8). If uncached, the recursor walks the hierarchy iteratively: root (→ TLD servers) → TLD (→ authoritative NS) → authoritative (→ the answer). Every hop is cached with a TTL. Transport is UDP/53 (TCP fallback for large/zone-transfer, EDNS0 for bigger UDP, DoT/DoH for encryption). "Propagation" isn't a thing — you're just waiting out cached TTLs.

01 The four actors

ActorRole
Stub resolverIn the OS/libc (getaddrinfo). Dumb: forwards one query, expects a full answer. Reads /etc/resolv.conf, nsswitch, hosts file. Has its own tiny cache (e.g. systemd-resolved, mDNSResponder).
Recursive resolverThe workhorse (ISP, 1.1.1.1, 8.8.8.8). Does the actual walking, holds the big cache. This is what resolv.conf points at.
Root & TLD serversAuthoritative only for delegations, not final answers. Root (13 named letters, anycast to 1000s of instances) points to TLD servers; TLD (.com) points to the domain's authoritative NS.
Authoritative NSHolds the real records for a zone (your Route53 / Cloudflare / NS1). The source of truth; sets the TTLs everyone else caches by.
Recursive vs iterative

The stub asks the recursor a recursive query ("give me the final answer"). The recursor asks root/TLD/auth iterative queries ("give me the answer or a referral to who's closer"). Two different query modes, one lookup.

02 A cold resolution, hop by hop

Resolving shop.example.com A record with an empty cache:

app → OS
getaddrinfo("shop.example.com")
Stub checks hosts file / its cache, then sends UDP query to the configured recursor.
↓ recursive query
recursor → root
"Where is shop.example.com?"
Root doesn't know, but returns a referral: the NS + glue for .com. (Root hints are baked in — the one bootstrap.)
↓ iterative
recursor → .com TLD
Same question to a .com server
Referral again: the authoritative NS records for example.com (+ glue A records so you don't loop resolving the NS names).
↓ iterative
recursor → authoritative
Same question to example.com's NS
This server is authoritative → returns the actual A record (answer section, aa flag set), with a TTL.
↓ cache every level, then
recursor → stub → app
Answer returned + cached
Recursor caches root/TLD/NS/A each by their own TTL. Next lookup for anything under .com or example.com skips the earlier hops.
Glue records

If example.com's NS is ns1.example.com, you can't resolve the NS without already resolving the zone → chicken/egg. The parent zone ships glue (the NS's A record) in the referral to break it.

03 Caching layers & TTL

A query can be answered at any of these — first hit wins, so debugging means knowing which layer lied to you:

TTL & negative caching

"DNS propagation" myth

Nothing propagates. Authoritative changes are instant. What you wait for is downstream caches expiring their TTL. Pre-lower TTL to 60s a day ahead → near-instant cutover.

04 Records you actually touch

TypePurpose / gotcha
A / AAAAname → IPv4 / IPv6.
CNAMEalias → another name. Can't coexist with other records at the same name, so never at the zone apex (apex needs SOA/NS). Providers fake apex aliasing via ALIAS/ANAME/flattening.
NSDelegation — who's authoritative for a zone. Must match at parent (registrar) and child.
SOAZone metadata: primary NS, serial, refresh/retry/expire, and the minimum (negative-cache TTL).
MXMail servers + priority. Points to a name (which needs an A/AAAA — not a CNAME per spec).
TXTSPF / DKIM / DMARC / domain verification. Watch the 255-char string chunking.
PTRReverse (IP → name) in in-addr.arpa. Matters for mail deliverability.

05 The wire format (why it behaves like it does)

One message, five sections. Query and response share the format — the response echoes your question and appends answers.

Header   id(16) | flags(QR OPCODE AA TC RD RA RCODE) | qd/an/ns/ar counts
Question QNAME (length-prefixed labels) | QTYPE | QCLASS
Answer   RRs: NAME TYPE CLASS TTL RDLENGTH RDATA
Authority NS RRs (referrals live here)
Additional glue / EDNS0 OPT pseudo-record

06 Transport & security

ModeWhen
UDP/53Default. Fast, one datagram. If response > 512B (classic) it sets TC.
EDNS0OPT record advertises larger UDP buffers (e.g. 1232B), avoiding TCP for moderately large answers. Required for DNSSEC.
TCP/53Truncated responses, and zone transfers (AXFR/IXFR between primary/secondary NS).
DoT / DoHDNS over TLS (853) / HTTPS (443). Encrypts stub↔recursor — privacy, not integrity.
DNSSECSigns records (RRSIG/DNSKEY/DS) so the resolver can verify authenticity. Failed validation → SERVFAIL. Solves spoofing, not privacy.

07 Incident-grade gotchas

The usual suspects

  • Change looks live at authoritative but users see old IP → cached TTL not expired.
  • Fixed a typo, still NXDOMAIN → negative cache (SOA minimum).
  • Apex CNAME rejected → use ALIAS/flattening.
  • Long-running JVM never re-resolves → networkaddress.cache.ttl.

Debug moves

  • dig +trace name — walk root→TLD→auth yourself.
  • dig @1.1.1.1 name vs @auth-ns — isolate cache vs source.
  • Check RCODE + AA flag, not just the IP.
  • Compare TTL countdown across repeated digs to spot who's caching.

Remember six things

  • Stub (dumb, recursive query) → recursor (walks, iterative queries) → root → TLD → authoritative.
  • Root/TLD give referrals, not answers; glue breaks the NS chicken-and-egg.
  • Everything is cached by TTL; NXDOMAIN is cached too (SOA minimum).
  • "Propagation" = waiting out downstream TTLs. Pre-lower TTL before changes.
  • CNAME can't live at the apex or beside other records.
  • UDP/53 default → TC → TCP; EDNS0 for bigger UDP; DoT/DoH encrypt, DNSSEC authenticates.