Skip to content
AtomicReps

Resolution is a protocol, not a lookup

A lesson from Wire Protocols & Applied Cryptography. Play it above, or read it through below.

Level I · Encoding, Transport and Name Resolution
  • TTL is a maximum, not a schedule.

    The common read: The TTL is 300, so five minutes after I change the record every cache re-reads it, and a cutover is an event I can plan a maintenance window around.

    DNS never pushes a change to caches. RFC 2181 says a TTL "specifies a maximum time to live, not a mandatory time to live": a cache may hold an answer that long and is never told to re-ask. Each cache's clock starts when that cache fetched, so one that fetched a second before your change may serve the old address for the whole window.

    RFC 9499 defines the stub resolver, the recursive resolver and the forwarder separately, and one request from a browser in a container behind a corporate forwarder can pass through all three plus the browser's own cache. Each holds its own copy, and you configure one of them: your authoritative server.

    RFC 8767 also lets a recursive resolver answer from expired cache while your authoritative servers are unreachable, so "DNS was fine during the incident" can mean it served data your zone had stopped publishing. When one percent of orders still reach the retired origin twenty minutes after a cutover, this is the cause, not the load balancer. Lower the TTL days ahead, and keep the retired origin serving until its requests stop.

  • A missing name is an answer, cached for a time you did not set.

    The common read: Nothing existed at that name, so nothing was cached, and the record I just created is live the moment the provisioning API returns.

    The absence was cached before your record existed. RFC 2308 makes the SOA the source of the lifetime, taking "the minimum of the MINIMUM field of the SOA record and the TTL of the SOA itself", and redefines SOA MINIMUM outright as "the TTL to be used for negative responses". It also tells resolvers to cap negative answers, suggests one to three hours, and calls anything over a day problematic.

    So one early check leaves the name failing for the smallest of three numbers: the SOA MINIMUM, the SOA record's own TTL, and the resolver's cap. A Route 53 zone with default settings gives 900 seconds. RFC 8020 extends the miss: on NXDOMAIN "all names and resource record sets (RRsets) at or below that node SHOULD be considered unreachable", including the child you are about to create.

    A retry is answered from the same cache entry and never reaches your zone. An ACME challenge that will not validate while the record is visible in the console is often this. Create the name before anything asks for it: create-then-delete costs an overlap window with a stale answer, and delete-then-create costs an outage whose length your SOA already set.

  • One name is not one query.

    The common read: A hostname costs one query, so the same call costs the same one query wherever it runs, and a resolver that hangs means the network is down.

    glibc turns one name into a list of queries. resolv.conf(5) gives ndots a default of 1, and a name with fewer than ndots dots is tried with each component of the search path in turn. Kubernetes ships pods with options ndots:5 and a three entry search list, so api.stripe.com carries two dots, falls below the threshold, and is asked as three cluster-local names that cannot exist before it is ever asked as itself.

    Double that count: the same manual says "glibc performs IPv4 and IPv6 lookups in parallel since glibc 2.9". RFC 8305 covers what a connecting client does with the pair: the AAAA query "immediately followed by the A query", no waiting for both, and a recommended 50 millisecond Resolution Delay when A answers first. While the nameserver answers, even to say the name does not exist, the extra queries cost almost nothing.

    When a nameserver drops a query without replying, timeout is 5 seconds of waiting "before retrying the query via a different name server", and attempts is 2 passes over the nameserver list. After the first search-list name gets no reply, glibc stops trying suffixes and asks the name as written once, and the A and AAAA queries share one wait.

    With one nameserver, as in a pod, that is two names at 5 seconds for each of 2 attempts: 20 seconds before the call fails. Put a trailing dot on each name that leaves the cluster, which changes only that name, or lower ndots for workloads that mostly call outward, which costs one failed query for an in-cluster name such as payments.team-b under ndots:1, asked as written before the search list resolves it.

  • A DNS answer can carry more than an address.

    The common read: A DNS answer carries addresses, so the protocol, the port and anything cryptographic are negotiated later, in TLS and in HTTP.

    A DNS answer can now carry the terms of the connection. RFC 9460 defines SVCB and its HTTPS specialisation, and its parameter list carries the terms of the connection: alpn and no-default-alpn say which protocols the endpoint speaks, port moves the service off the default, ipv4hint and ipv6hint carry addresses so the client does not have to ask twice, and mandatory lets the zone declare which parameters a client must understand or else ignore the record entirely.

    A client that reads the record before opening a socket can use HTTP/3 on a first visit. RFC 2181 lets a label hold one CNAME or other records, never both, and an apex must hold SOA and NS, so the ban on a CNAME at the apex is a protocol rule, not a registrar policy.

    ALIAS and ANAME are provider-side synthesis: the provider resolves your target from its own location, which sends users to the wrong edge when the target routes by geography. RFC 9460's AliasMode exists primarily for aliasing at a zone apex. Keep A and AAAA for clients that do not read the HTTPS record, and add a check that the two agree, because monitoring written for address records will not.

  • The redirect is gone. So is the encryption you thought you enabled.

    The common read: The connection is set up by my server and my edge, so what my zone publishes cannot remove a redirect or decide whether a name is encrypted.

    RFC 9460 says a client SHOULD look up the HTTPS record and, on finding one, SHOULD behave "as if it has received an HTTP 307 (Temporary Redirect)" to the https origin, so it never sends the plaintext request. Clients that do not follow this still get your 301, so 301 lines in the access log fall rather than stop.

    Encrypted Client Hello became an RFC in March 2026 and is bootstrapped from DNS: RFC 9849 delegates publication to RFC 9460, and RFC 9848, published the same month, defines the ech parameter, a key RFC 9460 had only reserved. A client that has no configuration does not fail; instead it SHOULD send a GREASE encrypted-client-hello extension and connect, with the real name in the clear where it always was.

    RFC 9849 notes that an attacker who can inject DNS responses can "strip the ECH configurations from the response", so a record that was never migrated and a record removed in transit produce the same outcome. RFC 9848 also marks a record set where only some records carry ech as NOT RECOMMENDED. Check the record from outside your network: an ECH toggle shows your edge's intent, and the record shows what a client can use.

  • The resolver may not be the OS's, so your override may not apply.

    The common read: The OS resolver is the one place DNS happens on a machine, so a hosts file entry and the corporate forwarder are still the chokepoint everything passes through.

    DNS has three encrypted transports: DoT on port 853 over TCP, DoH over HTTPS carrying application/dns-message, and DoQ on 853 over UDP. The OS stub can use some of them, and a browser can run its own resolver that skips the OS stub. DDR upgrades a client from its plaintext resolver to an encrypted endpoint run by the same operator, or one working with it, found by an SVCB query to _dns.resolver.arpa.

    DNR is the network announcing its resolver through DHCP or a Router Advertisement, and it takes precedence over DDR. Neither changes who answers. The single resolver is lost when a client picks its own, for example a browser whose DoH setting points at a public provider.

    /etc/hosts is read by the OS resolver, and by a browser's own resolver only if the browser was built to: Firefox reads it once, when its DoH resolver is turned on, so a later entry is not seen until the browser restarts. The browser also skips split-horizon, since a view is selected by the query's source address: VPN users get an internal service's public address and time out. A hosts file or an internal view is a default some clients accept, not an enforcement point.