The Runtime Theory
Networking

DNS Resolution End to End: From resolv.conf to Recursive Lookup

What really happens when you resolve a hostname — resolvers, cache TTLs, CNAME chains, EDNS, TCP fallback on truncation, and how DoH changes the path.

The Runtime Theory Team3 min read#dns#resolvers#caching#doh#networking
On this page

Most engineers treat DNS as a magic function: give it a name, get an IP. In reality, the name never gets "looked up" — it gets asked about through a chain of independent systems, each with its own cache, its own timeout, and its own failure modes. This is the whole path, step by step, from your process to the root servers.

Step 1: the resolver configuration

Before anything else, the kernel's resolver needs to know who to ask. On Linux that's /etc/resolv.conf:

bash
nameserver 127.0.0.53     # systemd-resolved stub, forwards upstream
nameserver 8.8.8.8        # fallback, used only if the first fails
options timeout:2 attempts:2 ndots:1

Two details matter here. First, ndots:1 means a name with at least one dot (like api.theruntimetheory.com) is treated as fully qualified and queried as-is; a bare hostname like db is tried against the search domains first. Second, timeout:2 attempts:2 means each upstream gets 2 seconds, twice, before the resolver gives up and returns an error to your application. A slow DNS server shows up in your request latency as a hard, fixed number — not a gradual slowdown.

Step 2: caches are where most lookups end

The vast majority of DNS queries never leave the machine. glibc's nscd or systemd-resolved keeps a local cache keyed by (name, type) with entries that expire according to the record's TTL — the number of seconds the authoritative server declared the answer valid. The cache is a performance layer, but it's also a consistency layer: as long as the TTL hasn't expired, every process on the machine sees the same IP, even if the DNS zone changed upstream five seconds ago.

Step 3: the recursive walk

If the local cache misses, the resolver performs a recursive resolution. Every resolver (starting from the stub on your machine, through your ISP's or Cloudflare's recursor) does the same walk:

text
client -> resolver: "what is api.theruntimetheory.com?"
resolver -> root server: "who serves .com?"
resolver -> .com TLD server: "who serves theruntimetheory.com?"
resolver -> theruntimetheory.com authoritative server:
             "what is api.theruntimetheory.com?"
authoritative -> resolver: A 104.21.62.13  (TTL 300)
resolver -> client: A 104.21.62.13

Each answer is a referral to the next zone, and the resolver caches every step of the walk. That's why a root server outage is survivable in practice: the root zone is cached for days, and only the TLD step needs refreshing.

Step 4: CNAME chains are extra lookups, not redirects

A CNAME record is an alias: it tells the resolver "the real name is X, keep going." This is how every CDN works:

bash
$ dig +short blog.theruntimetheory.com
blog.theruntimetheory.com.cdn.cloudflare.net.   # CNAME
104.21.62.13                                    # A record, different zone

Critically, the client sees only the final A record — the extra lookup happens entirely inside the resolver, and each hop in the chain is cached separately with its own TTL. A CNAME pointing at a CDN with a 60-second TTL means your edge DNS is effectively uncached: every miss walks the chain again.

Step 5: EDNS and the TCP fallback

Almost all queries now carry EDNS(0), which negotiates a larger UDP payload (up to 4096 bytes) so answers fit in a single packet. But large answers still exceed UDP limits, and middleboxes still mangle big UDP packets. The protocol's escape hatch: the server responds with a truncated answer — the TC bit set — and the resolver is required to retry the query over TCP, where answers can be as big as needed.

bash
$ dig +ignore +tcp example.com AAAA   # force the TCP path

You can trigger this yourself with a large TXT record: the first response arrives with truncated: 1 and the resolver silently retries on TCP. When you see "timeout resolving" errors, it's often this fallback failing — the UDP query worked, but the TCP retry hit a firewall that blocks port 53/TCP.

Step 6: the protocol change — DoH and DoT

Classic DNS sends queries in clear UDP to port 53 — visible to your ISP, your Wi-Fi operator, and any middlebox. DoH (DNS over HTTPS) wraps the same query in HTTPS to port 443; DoT uses TLS on 853. The resolver path stays identical; only the last hop (client → resolver) becomes encrypted. The trade-off: DoH hides the query from the network, but hands it to a single resolver (your chosen DoH provider), which sees everything.

The mental model

  • resolv.conf picks the resolver; TTLs decide how often it's actually consulted.
  • CNAMEs are resolution-time indirection, invisible to the application.
  • Truncation + TCP is the protocol's designed fallback, and the most common silent DNS failure.
  • Every hop — stub, recursor, TLD, authoritative — is a separate cache with its own staleness window. "Propagation" is not magic; it's a countdown of TTLs.