Skip to content

DNS resolution end to end: from stub resolver to authoritative answer

· 23 min read
Copyright: MIT
The letters DNS as a large monospace wordmark with a single orange arrow tracing a query through root, TLD, and authoritative tiers

You type a hostname, press enter, and a few milliseconds later your machine has an IP address it never had before. Nothing about that exchange is obvious. Your computer did not ask one server a question and get an answer. It handed the question to a piece of software so minimal it cannot even follow a referral, which handed it to another server that may have walked half the planet’s naming hierarchy to find the response, consulting a chain of machines that each knew only one small thing and pointed at the next one. The whole transaction usually finishes faster than the page can paint.

That distance, between the single question your application asked and the multi-hop walk that actually answered it, is the subject of this post. DNS is a distributed, hierarchical, heavily cached database, and the lookup you trigger is a small recursive algorithm running across servers owned by different organizations on different continents. The interesting part is not that it works. The interesting part is how little each participant has to know for the whole thing to resolve.

This post walks one lookup from start to finish. It begins with the stub resolver inside your operating system, follows the query to a recursive resolver, watches that resolver descend from the root through a top-level domain to an authoritative server, and then comes back to caching, TTLs, negative answers, and the resource record types that carry the actual data. Along the way it notes where the modern system has drifted from the 1987 specifications that still define it.

The stub resolver: the client that cannot walk the tree

The thing in your operating system that resolves names is called a stub resolver, and the word “stub” is doing real work. It is deliberately incapable. A stub resolver knows how to format a query, send it to a configured server, wait for a response, and hand the answer back to whatever called getaddrinfo(). What it cannot do is follow the hierarchy itself. If a server answers with a referral instead of an address, the stub does not know how to take the next step. It expects a final answer and nothing else.

This is by design, and the design is old. RFC 1034, published in November 1987, splits the resolution machinery into a full resolver that can navigate the name space and a stub that offloads that navigation to a recursive server. The stub sets one bit in its query, the RD bit (Recursion Desired), which asks the receiving server to do the walking on its behalf. Everything downstream of that bit is the recursive resolver’s problem.

On a typical machine the stub’s configured upstream comes from DHCP, from /etc/resolv.conf on a Unix-like system, or from network settings pushed by an administrator. The query itself is a UDP datagram by default, sent to port 53. It carries a 16-bit identifier the client picks, a flags word that includes RD, and a single question: a name, a type, and a class. The class is almost always IN, for Internet. The type is what you are actually asking for, most commonly A for an IPv4 address or AAAA for IPv6.

A query message, the parts the stub fills in ID 16-bit, client-chosen FLAGS: QR=0 RD=1 recursion desired QDCOUNT=1 one question QUESTION QNAME = www.example.com QTYPE = A QCLASS = IN The stub sets RD and asks one question. The recursive resolver does the rest.

The stub resolver builds a minimal query: an ID, the RD flag, and one question. It does not know how to follow referrals, so it relies on the recursive resolver to return a final answer.

There is a subtlety here that bites people. The stub does not validate much. Historically it accepted any response that arrived on the right socket, carried the matching 16-bit ID, and echoed the question. That is a small amount of entropy to forge, which is exactly why off-path attackers spent years trying to race legitimate answers. Source-port randomization, added broadly after 2008, widened the entropy the attacker has to guess. The stub still does not cryptographically verify anything on its own; that job belongs to DNSSEC validation, which usually happens at the recursive resolver, not in the stub.

Handing off to the recursive resolver

The recursive resolver is the machine that actually does the work. When your stub’s query lands on it, that resolver becomes responsible for producing a final answer, no referrals allowed back to the stub. RFC 1034 calls this recursive mode: the server “pursues the query on behalf of the client,” returning either the answer or an error. The contrast is iterative mode, where a server answers with whatever it has locally, which may be a referral telling the asker where to look next. The resolver uses iterative queries to talk to the rest of the hierarchy, and recursive mode to talk to you.

Most people use a recursive resolver they did not choose. It is the one their ISP handed them over DHCP. Others point their machines at a public resolver: Google’s 8.8.8.8, Cloudflare’s 1.1.1.1, Quad9’s 9.9.9.9. Cloudflare launched 1.1.1.1 on the symbolically appropriate date of April 1, 2018, and the appeal of these services is partly performance and partly privacy policy, since the resolver sees every name you look up. The choice matters more than it looks, because the resolver is where caching lives, where DNSSEC validation happens, and where increasingly the transport gets encrypted. That last point gets its own treatment in DNS-over-HTTPS and DNS-over-TLS; for the rest of this post assume classic port-53 transport unless noted.

The first thing a good recursive resolver does with your query is check its cache. If it already knows the answer and the cached record has not expired, it answers immediately and the walk never happens. Caching is not a side feature of DNS. It is the reason the root servers are not melted into slag by the world’s query volume. The full descent through root, TLD, and authoritative servers is the cold-cache path, the thing that happens on a miss. On a busy resolver most popular names are warm, and the answer comes straight from memory.

But assume a cold cache. The resolver knows nothing about the name. It has to start where every resolver starts: at the top.

Where every walk begins: the root

A resolver that knows nothing still knows one thing. It ships with a small built-in list called the root hints, a set of names and addresses for the root servers. This is the only configuration a resolver needs to bootstrap the entire name space. From the root it can find everything else.

The root is served by 13 named identities, lettered A through M, from a.root-servers.net to m.root-servers.net. The number 13 is not arbitrary and it is not a count of machines. It is a consequence of packet size. The early DNS specification capped a UDP response at 512 octets, and the list of root server addresses plus the overhead of the message had to fit inside one unfragmented packet. Thirteen IPv4 addresses was roughly what fit. The constraint is documented plainly: the size of the UDP data packets means there is only room to include the addresses of 13 root servers in a single packet. The letters are an artifact of a byte budget from the 1980s, frozen into the architecture.

Thirteen identities does not mean thirteen machines. Each lettered identity is a single IP address announced from many physical locations using anycast, so the routing system delivers your packet to whichever instance is closest in network terms. The multiplication is enormous. Netnod’s root server FAQ recorded 372 root server instances worldwide by early August 2014, and the count has only grown since; recent figures put the global total near 2,000 instances. The distribution is wildly uneven by design: at one point there was a single instance of the B identity while the L identity ran 145. Anycast is what turns 13 addresses into a planet-spanning fleet, and it is worth a separate read in Anycast routing.

Twelve organizations operate the 13 identities. Verisign runs two (A and J); the others are split among USC-ISI, Cogent, the University of Maryland, NASA Ames, Internet Systems Consortium, the US Department of Defense NIC, the US Army Research Lab, Netnod, RIPE NCC, ICANN, and the WIDE Project. No single operator controls the root, which is a deliberate piece of governance, not an accident of growth.

When the resolver sends its first iterative query to a root server, it does not ask the root for the address of the full hostname. The root does not know that. The root knows one thing: who is authoritative for each top-level domain. So the root answers with a referral. For a query about www.example.com, the root responds with the names and addresses of the servers responsible for the .com zone. It hands back NS records pointing at the TLD servers, plus their addresses in the additional section so the resolver does not have to look those up separately. This bundling of addresses is called glue, and without it the resolver would face an infinite regress of needing to resolve the name of the server that resolves names.

The cold-cache descent for www.example.com recursive resolver root server knows the TLDs .com TLD server knows the zone NS authoritative knows the A record 1 ? com 2 referral .com 3 ? example.com 4 referral example 5 ? www 6 A 93.184.x.x Each tier answers only what it knows and points down. The resolver climbs back to the stub with one address.

A cold-cache lookup descends one level at a time. Root refers to the TLD, the TLD refers to the zone’s authoritative servers, and only the authoritative server returns the address. The resolver does all the walking; the stub sees only step 6.

The TLD server: one level down

Now the resolver has the addresses of the .com nameservers, which Verisign operates. It repeats the pattern. It sends an iterative query for the name to one of those TLD servers. The TLD server, like the root, does not hold the address of www.example.com. It holds the delegation for example.com: the NS records naming the authoritative servers that the domain’s owner registered, again with glue addresses where needed.

This is the registration boundary made concrete. When someone registers example.com, the registrar tells the registry which nameservers are authoritative for the zone, and the registry publishes those NS records in the TLD zone. The TLD server is the public record of that delegation. It is why changing your nameservers at your registrar eventually changes where the world’s resolvers go to ask about your domain. The TLD is a pointer, nothing more, but it is the pointer the entire internet trusts for that top-level domain.

There is a privacy wrinkle worth naming. In the original design, the resolver sent the full name, www.example.com, to every server in the chain, including the root and the TLD. The root does not need to see www; it only needs the com part to give a referral. Sending the whole name leaks more than necessary to servers that do not need it. RFC 9156, published in 2021 and obsoleting the earlier RFC 7816, defines QNAME minimisation: the resolver sends only the labels a given server needs. To the root it asks about com. To the .com server it asks about example.com. Only the authoritative server ever sees www. The change lives entirely in resolver behavior; the protocol on the wire is unchanged, which is why it could deploy without coordination. The cost is that a cold resolver sends one query per label instead of one query for the whole name. Major resolvers, including the large public ones, run QNAME minimisation by default now.

The authoritative server: the final answer

The resolver now has the addresses of the nameservers that example.com’s owner designated. It sends its iterative query one more time, and this server is different from the ones before it. It is authoritative. It does not refer the resolver onward; it owns the zone and returns the actual record. For an A query it sends back the IPv4 address. It sets the AA bit, the Authoritative Answer flag, which tells the resolver this came from a server that holds the zone’s official data rather than a cached copy.

“Authoritative” is a precise term in DNS, not a marketing one. A zone is a contiguous region of the name space under one administrative control, and the servers configured to serve that zone’s data are authoritative for it. RFC 1034 puts it directly: the zone is authoritative for all names in its connected region. Authoritative data is the source of truth. Everything else in the system, every cache, is a time-limited copy of what some authoritative server said.

The authoritative server’s answer is not always a single address. The owner may publish several A records for one name, and the resolver and client between them pick one, which is the crudest form of load distribution. The owner may publish a CNAME, an alias that says “this name is really that other name, go resolve that instead.” The owner may answer differently depending on where the query came from, which is the basis of GeoDNS and is covered in How DNS load balancing and GeoDNS steer traffic. The point is that the authoritative server is the only participant entitled to invent the answer. Everyone upstream of it was just pointing the way.

What a resource record actually is

Up to here the walk has dealt in vague terms like “the address” and “the delegation.” The concrete unit underneath all of it is the resource record, the RR. Every piece of data in DNS is an RR, and every RR has the same five-part shape. There is an owner, the name the record is attached to. There is a type, which says what kind of data this is. There is a class, almost always IN. There is a TTL, a 32-bit integer in seconds. And there is the RDATA, the type-specific payload.

The five fields of every resource record OWNER the name TYPE A, NS, MX... CLASS IN TTL 32-bit seconds RDATA type-specific example, an A record on the wire: www.example.com. 300 IN A 93.184.216.34

Every record in DNS, an address, a delegation, a mail route, is the same five-field structure. Only the TYPE and RDATA change. The TTL governs how long a cache may keep it.

A few RR types carry most of the traffic, and each does one job. The A record maps a name to a 32-bit IPv4 address; for the IN class the RDATA is literally those four bytes. AAAA does the same for IPv6 with a 128-bit address. NS names an authoritative server for a zone, and these are the records that form the delegation chain the resolver followed down from the root. CNAME marks its owner as an alias and gives the canonical name to resolve instead. MX names a mail exchange with a preference number, the lower number preferred. PTR points a name at another name, the workhorse of reverse lookups that map an address back to a hostname. TXT holds arbitrary text and has become the catch-all for verification tokens, SPF policies, and a hundred other things that needed a place to live in DNS.

One type sits apart: SOA, the Start of Authority. Every zone has exactly one, at the zone apex, and it carries the zone’s administrative parameters. It names the primary nameserver and the responsible party’s email. It carries a serial number that secondary servers compare to decide whether to pull a fresh copy of the zone. And it carries timing values for refresh, retry, and expiry of zone transfers, plus a final field that originally meant the default TTL for records but now governs something more specific, which the next section gets to.

Caching and TTLs: why the root survives

The TTL is the most consequential number in DNS and the one people most often get wrong. It is a 32-bit count of seconds attached to every record, set by the zone administrator, and it tells any resolver that caches the record how long it may keep it before the copy must be discarded and re-fetched. RFC 1034 is blunt about its purpose: the TTL “describes how long a RR can be cached before it should be discarded,” and it is “primarily used by resolvers when they cache RRs.”

That single mechanism is what keeps the hierarchy standing. The root and TLD servers handle a staggering query rate, but the delegations they serve change rarely and carry long TTLs, often a day or two. A resolver that learns where .com’s servers are does not ask again for a long time. The expensive cold-cache walk happens once per name per TTL window per resolver, and after that the answers come from memory. Without caching, every lookup on earth would hammer the root. With it, the root mostly sleeps.

The trade-off is freshness against load, and it is entirely in the zone owner’s hands through the TTL value. A long TTL means fewer queries and faster average responses, but a change you make propagates slowly because every resolver out there is still serving the old answer until its cached copy expires. A short TTL, say 60 seconds, means you can move a name quickly, which is why services that fail over between IPs or steer traffic dynamically keep their TTLs low and accept the extra query volume. There is no global flush. When people say a DNS change “propagated,” what actually happened is that caches around the world hit their TTL and re-fetched. The owner controls how long that takes only by what TTL they published before the change.

TTL is a bet: query load against change speed TTL = 86400 (one day) one cached copy serves the whole day, almost no upstream queries TTL = 60 (one minute) re-fetched constantly, but a change reaches everyone within a minute Each orange tick is a fresh upstream query. There is no global flush; caches simply expire.

A long TTL trades slow propagation for low query volume; a short TTL does the reverse. The zone owner chooses the trade-off in advance by what TTL they publish, because there is no way to force the world’s caches to drop a record early.

Negative answers also get cached

Caching a “yes” is obvious. Caching a “no” is less obvious and just as important. If a name does not exist, the authoritative server returns NXDOMAIN. If the name exists but has no record of the type asked for, the answer is NODATA, an empty answer with a success code. Both are real answers, and a resolver that did not cache them would re-ask the authoritative servers every single time some client probed a name that is not there, which is a common pattern given typos, stale links, and misconfigured software.

RFC 2308, from 1998, made negative caching mandatory and tied it to the SOA record. When an authoritative server returns NXDOMAIN or NODATA, it includes the zone’s SOA in the authority section. The resolver caches the negative answer, and the TTL it uses for that cache is the lesser of the SOA’s own TTL and the SOA’s final field, the MINIMUM field. That last field, which originally meant a default TTL for all records in the zone, was repurposed by RFC 2308 to mean the negative-caching TTL specifically. It is a small piece of history that still trips people up: the SOA MINIMUM does not set a floor on positive TTLs anymore; it caps how long a “this does not exist” answer sticks around. And a negative answer that arrives without an SOA is not supposed to be cached at all, because there would be no defined TTL to bound it.

When the answer does not fit: TCP and EDNS

The walk above assumed UDP, and most of the time UDP is the whole story. A query goes out in one datagram, an answer comes back in one datagram, done. But DNS over UDP started with a hard ceiling. RFC 1035 capped a UDP message at 512 octets. When a response is larger than what the channel allows, the server sets the TC bit, the truncation flag, and the client is expected to retry the query over TCP, where length is not a problem. That is the original fallback: truncate, then re-ask on a connection.

512 bytes is small, and modern DNS routinely needs more, especially once DNSSEC signatures and large record sets enter the picture. The fix is EDNS(0), the extension mechanism standardized in RFC 6891 in 2013. EDNS does not change the header; it smuggles its extra information into a pseudo-record called OPT, placed in the additional section of the message. Among other things, OPT lets the client advertise a larger UDP payload it is willing to receive. The commonly recommended starting size is 4096 octets, with a fallback to something in the 1232 to 1410 range if the large size fails to get through, since smaller sizes have a better chance of fitting inside a single IP packet without fragmentation. EDNS is also the carrier for DNSSEC’s DO bit and for a long tail of other options, so in practice almost every modern query on the wire is an EDNS query.

There is a fingerprinting angle worth a footnote for anyone who works adjacent to bot detection. The exact EDNS buffer size a client advertises, whether it sets the DO bit, how it behaves on truncation, all of that varies between resolver implementations and can distinguish one stack from another. The same logic that fingerprints a TLS ClientHello applies to DNS behavior, though DNS sits a layer below most application-level detection and is observed far less often.

The lengths and limits that shape everything

A handful of fixed numbers from the original specification still constrain the whole system, and they explain design choices that would otherwise look arbitrary. A single label, the part between two dots, is capped at 63 octets. A full domain name is capped at 255 octets total. The 512-octet UDP limit, already discussed, is what froze the root server count at 13. These are not suggestions; they are encoded in the wire format. The 63-octet label limit, for instance, exists because the length prefix on each label uses the low 6 bits of a byte, and the top two bits are reserved for the compression-pointer mechanism that lets a message reuse a name it already spelled out. The format and the limits are the same artifact.

The header carries its own set of flags that the walk touched on in passing. QR distinguishes a query from a response. OPCODE says what kind of query it is, with 0 for a standard query. AA is the authoritative-answer bit. TC is truncation. RD is recursion-desired, set by the stub. RA is recursion-available, set by a resolver that will do recursion for you. RCODE carries the result, 0 for success and a small set of error codes including the one for NXDOMAIN. Four count fields say how many records each of the four sections holds. None of this has changed structurally since 1987. New capabilities arrive as new record types or as EDNS options, almost never as changes to the header, because the header’s compatibility is load-bearing for the entire installed base.

What the walk leaves out, and where it is going

The lookup traced here is the classic one, and it is still what happens underneath, but the edges have moved. The biggest change is transport. Classic DNS is plaintext on port 53, which means anyone on the path between your stub and your resolver can see every name you look up and, with enough effort, forge answers. DNS-over-TLS and DNS-over-HTTPS wrap the stub-to-resolver hop in encryption, which closed a long-standing surveillance and tampering channel and, as a side effect, broke the network-level DNS filtering that schools, enterprises, and some governments relied on. That story is its own post: DNS-over-HTTPS and DNS-over-TLS. Encrypted Client Hello pushes further, hiding even the server name in the TLS handshake, which removes one of the last plaintext signals about where you are connecting.

The other quiet change is who the authoritative answer really comes from. The walk assumed a static A record sitting in a zone file. In practice the authoritative server for a large site is frequently a managed DNS provider running anycast, returning different addresses based on the resolver’s location, the health of backend pools, and traffic-steering policy. The record you get for a hostname depends on where you asked from and what the steering logic decided in that instant. The hierarchy down from the root is unchanged; the leaf at the bottom got a lot smarter. The interaction between that steering and the CDNs it usually feeds is the subject of How a CDN actually works.

What has not changed is the shape of the thing. A stub that cannot walk hands a question to a resolver that can. The resolver starts at a root it found in a hints file, gets pointed at a TLD, gets pointed at a zone, and finally gets an answer from a server entitled to give one. Caching keeps the upper tiers idle most of the time, and TTLs decide how long the whole world believes an answer that may already be stale. The system is almost forty years old, it is patched in a dozen places, and the next time you resolve a name it will run exactly this algorithm, mostly out of a cache, faster than you can notice. The most remarkable property of DNS is not that it scales. It is that a design constrained by the size of a 1987 UDP packet still answers the entire internet’s questions, one referral at a time.


Sources & further reading

Further reading