How DNS works
DNS turns a name like www.example.com into an IP address. This guide follows one lookup with empty caches from the browser out to the root servers and back, then covers record types, caching and TTLs, DNSSEC and encrypted DNS, and how to debug lookups with dig.
What DNS does
The Domain Name System (DNS) is the distributed database that maps names such as www.example.com to the data needed to reach them, most often an IP address. Computers connect to IP addresses; people and configuration files use names. Every time a browser opens a site, a phone checks for mail or a service calls another service by name, a DNS lookup happens first.
No single server holds all the names. The data is split into zones, each run by whoever owns that part of the name, and the servers for each zone only know their own records plus where to find the zones below them. A lookup with nothing cached starts at the top of the tree and is referred downwards, one level at a time, until it reaches the server that holds the answer.
That would be slow if it happened on every request, so DNS relies on caching. Every answer carries a time to live (TTL) saying how long it may be reused, and most lookups are answered from a cache without touching the servers further down the chain.
DNS was designed by Paul Mockapetris in 1983 to replace a single HOSTS.TXT file that every computer on the ARPANET downloaded from one central host. The current core standards, RFC 1034 and RFC 1035, date from 1987, and most of what follows still works the way they describe.
How names are organised
DNS names form a tree, read from right to left. Each part between the dots is a label. In www.example.com, the top of the tree is the root, written as an empty label after a final dot that is usually left out (www.example.com.). Below the root are the top-level domains (TLDs) such as com, org and in; there are more than 1,400 of them. Below com is example, and below that www.
A zone is a part of the tree managed as one unit. The root zone lists every TLD and the servers for each. The com zone, run by Verisign, lists every .com domain and its nameservers, but none of the records inside those domains. The example.com zone, run by whoever owns the domain or their DNS provider, holds the actual records such as the address of www.example.com. Handing a part of the tree to another set of servers is called delegation, and it is done with NS records in the parent zone.
When you buy a domain, the company you buy it from is a registrar. It passes your chosen nameservers to the registry that runs the TLD, and the registry publishes them in the TLD's zone. That is the only part of your DNS that lives outside your own provider: if the nameservers listed at the registry are wrong, nothing you change at your provider will be seen.
The servers in a lookup
Four kinds of software take part in a typical lookup. Only the last three are what people usually mean by "DNS servers", and only the last one holds the record being asked for.
| Role | Example | What it does |
|---|---|---|
| Stub resolver | The operating system's resolver library or service | Takes a name from an application and sends it to a recursive resolver. Keeps a small cache. |
| Recursive resolver | Your ISP's resolver, 1.1.1.1, 8.8.8.8, 9.9.9.9, or one inside a company or cloud network | Finds the answer by asking authoritative servers in turn, starting at the root. Caches every answer, shared by all its clients. |
| Root servers | a.root-servers.net to m.root-servers.net | Authoritative for the root zone. Answer with a referral to the right TLD's servers. |
| TLD servers | a.gtld-servers.net and others for .com | Authoritative for a TLD. Answer with a referral to the domain's own nameservers. |
| Authoritative servers for the domain | The domain owner's servers or a DNS provider | Hold the domain's records and give the final answer. |
The recursive resolver does almost all the work. It is "recursive" because it accepts a question and promises to come back with the final answer; the servers it asks are iterative, meaning each one answers only what it knows and points the resolver somewhere else for the rest.
The steps below follow one lookup of www.example.com with every cache empty. The root and .com server names and addresses are real. The servers for example.com are written as ns1.example.com and ns2.example.com, and their addresses come from the ranges reserved for documentation, so they are illustrative.
Step 1: your computer
The browser needs an address for www.example.com. Before anything goes over the network, three local places are checked.
- The browser's own cache of recent lookups. Chrome and Firefox keep one, separate from the operating system's.
- The hosts file (/etc/hosts on Linux and macOS), a leftover of the pre-DNS days that still overrides DNS for any name listed in it.
- The operating system's resolver cache, if it has one: systemd-resolved on many Linux systems, mDNSResponder on macOS, the DNS Client service on Windows.
All three miss, so the stub resolver sends a query to the recursive resolver it has been configured with, usually handed out by the router over DHCP or set by hand (on Linux it is the nameserver line in /etc/resolv.conf). The query is a single UDP packet to port 53. It carries a random 16-bit ID, comes from a random source port, and has the RD (recursion desired) flag set, which asks the resolver to do the whole lookup and return only the final answer.
Browsers usually ask for two record types at once: an A record for the IPv4 address and an AAAA record for the IPv6 address, so they can try both. Some also ask for an HTTPS record, which can tell them the site supports HTTP/3 before they connect. The rest of this guide follows the A query.
Step 2: a root server
The resolver checks its own cache. It has nothing for www.example.com, nothing for example.com and nothing for com, so it has to start at the top. It knows the root servers' addresses from a root hints file shipped with the software, and when it starts it asks one of them for the current list (this is called priming).
It picks a root server, say a.root-servers.net at 198.41.0.4, and asks for the A record of www.example.com, this time without the RD flag. The root server does not know the answer and does not go looking for it. It replies with a referral: the names of the servers for com, plus their addresses.
$ dig +norecurse @a.root-servers.net www.example.com A ;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 13, ADDITIONAL: 27 ;; AUTHORITY SECTION: com. 172800 IN NS a.gtld-servers.net. com. 172800 IN NS b.gtld-servers.net. ; 11 more NS records for com. ;; ADDITIONAL SECTION: a.gtld-servers.net. 172800 IN A 192.5.6.30 a.gtld-servers.net. 172800 IN AAAA 2001:503:a83e::2:30 ...
The answer section is empty, and the response lacks the aa (authoritative answer) flag, which is how the resolver knows this is a referral and not the final answer. The NS records go in the authority section, and the addresses of those nameservers come along in the additional section so the resolver does not need a separate lookup to find them. The number 172800 is the TTL in seconds: the resolver may remember the .com servers for two days.
Older resolvers send the full name to every server, as above, which tells the root servers every name their users look up. Many current resolvers, including BIND and Unbound in their default settings, use QNAME minimisation (RFC 9156) instead: they ask the root only about com, ask the .com servers only about example.com, and send the full name only to the server that holds it.
Step 3: the .com servers
The resolver now asks one of the .com servers, say a.gtld-servers.net at 192.5.6.30. That server holds the delegation for every .com domain but none of the records inside them, so it too answers with a referral, this time to the nameservers for example.com.
$ dig +norecurse @a.gtld-servers.net www.example.com A ;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 3 ;; AUTHORITY SECTION: example.com. 172800 IN NS ns1.example.com. example.com. 172800 IN NS ns2.example.com. ;; ADDITIONAL SECTION: ns1.example.com. 172800 IN A 192.0.2.53 ns2.example.com. 172800 IN A 198.51.100.53
The additional section matters more here. The nameservers for example.com are themselves inside example.com, so without their addresses the resolver would be stuck: to find ns1.example.com it would need to ask ns1.example.com. The addresses that break the loop are called glue records, and the registrar sends them to the registry along with the NS records. Domains whose nameservers are under another name, such as a DNS provider's, do not need glue; the resolver just looks up the provider's nameservers separately.
Step 4: the authoritative server
The resolver asks ns1.example.com at 192.0.2.53. This server holds the example.com zone, so it has the record and answers with it. The aa flag is set this time.
$ dig +norecurse @192.0.2.53 www.example.com A ;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; ANSWER SECTION: www.example.com. 3600 IN A 192.0.2.10
The resolver stores the answer in its cache for 3,600 seconds, the TTL the domain owner chose, and sends it back to the stub resolver with the RA (recursion available) flag set. The stub resolver caches it too, hands the address to the browser, and the browser opens a TCP or QUIC connection to 192.0.2.10.
If the name had not existed, the authoritative server would have answered with the status NXDOMAIN and included the zone's SOA record, and the resolver would cache that negative answer too (see TTLs and caching). If the name existed but had no A record, the status would be NOERROR with an empty answer section, sometimes called NODATA.
Had any server failed to answer, the resolver would have tried another of the zone's nameservers; that is why every zone has at least two. The stub resolver has its own, much slower retry: with the default settings in resolv.conf, glibc waits 5 seconds before trying again, which is why a broken resolver tends to show up as pages that take exactly 5 or 10 seconds to start loading.
Step 5: the next lookup
The first lookup took four round trips from the resolver, one to each server and one back to the client. Very few lookups look like that, because the resolver's cache is shared by everyone who uses it.
- Someone else asks for www.example.com within the hour. The resolver answers from its cache straight away, without contacting any other server. The TTL in the answer counts down: a client asking 20 minutes later is told 2400, not 3600, so no cache downstream keeps the record longer than the owner allowed.
- Someone asks for mail.example.com. The resolver still has the NS records for example.com, so it goes straight to ns1.example.com. One round trip.
- Someone asks for a different .com domain. The resolver has the .com servers cached for two days, so it skips the root and starts at step 3.
Root servers are rarely asked about popular TLDs for this reason; a resolver needs to ask them about com only about once every two days. A large share of the queries root servers do receive are for TLDs that do not exist, often from software that tries a misconfigured name or a local name such as printer.local against public DNS.
Record types
Every record has a name, a type, a class (almost always IN, for internet), a TTL and the data itself. A lookup asks for one name and one type. These are the types most engineers deal with.
| Type | Holds | Example data |
|---|---|---|
| A | An IPv4 address | 192.0.2.10 |
| AAAA | An IPv6 address | 2001:db8::10 |
| CNAME | Another name that this one is an alias for. The resolver restarts the lookup with the target name. | shop.example.net. |
| NS | A nameserver for the zone | ns1.example.com. |
| SOA | Zone settings: primary server, contact, serial number, refresh timers and the negative-caching TTL | ns1.example.com. admin.example.com. 2026101101 ... |
| MX | A mail server and its preference; lower numbers are tried first | 10 mx1.example.com. |
| TXT | Free text. Used for SPF, DKIM and DMARC email policies and for proving domain ownership. | "v=spf1 include:_spf.example.net -all" |
| CAA | Which certificate authorities may issue certificates for the name (RFC 8659) | 0 issue "letsencrypt.org" |
| SRV | Host and port for a named service (RFC 2782) | 10 5 5060 sip.example.com. |
| PTR | A name for an address, used for reverse lookups under in-addr.arpa and ip6.arpa | www.example.com. |
| HTTPS | Connection hints for a web service: supported protocols such as HTTP/3, addresses, and an alternative endpoint (RFC 9460) | 1 . alpn="h3,h2" |
A zone file, the text format from RFC 1035 that many servers still load, puts these together. The @ stands for the zone's own name, and names without a final dot have the zone name appended.
$ORIGIN example.com. $TTL 3600 @ IN SOA ns1.example.com. admin.example.com. ( 2026101101 ; serial 7200 ; refresh: secondaries check for changes 900 ; retry 1209600 ; expire: secondaries stop answering after 14 days 300 ) ; negative-caching TTL @ IN NS ns1.example.com. @ IN NS ns2.example.com. ns1 IN A 192.0.2.53 ns2 IN A 198.51.100.53 @ IN A 192.0.2.10 www IN A 192.0.2.10 @ IN MX 10 mx1 mx1 IN A 192.0.2.25 shop IN CNAME shop.example.net.
A CNAME cannot sit alongside any other record at the same name (RFC 1034). The zone's own name, example.com itself, always has SOA and NS records, so it cannot be a CNAME. That is why pointing a bare domain at a load balancer's hostname needs a provider feature such as an ALIAS record or "CNAME flattening", where the provider looks up the target itself and serves the result as A and AAAA records.
TTLs and caching
Every record's TTL is chosen by the zone's owner. It is the longest time any cache may keep the record. Common values run from 60 seconds, for records that may need to change quickly, to 86,400 seconds (a day) for records that rarely change. The .com delegations above have a TTL of two days.
There is no "propagation" in DNS. When a record changes, nothing is sent to resolvers. Each resolver that cached the old record keeps serving it until the TTL runs out, then asks again and gets the new one. A change to a record with a one-day TTL can therefore take up to a day to reach everyone, and different users see the change at different times. The usual approach before a planned move is to lower the TTL to a few minutes at least one old TTL in advance, make the change, and raise the TTL again afterwards.
Negative answers are cached as well (RFC 2308). An NXDOMAIN is kept for the smaller of the SOA record's TTL and its last field, the negative-caching TTL. If something looks a name up before you create it, resolvers will keep saying it does not exist for that long.
A TTL is only the maximum. Some resolvers set a minimum and keep records longer than very short TTLs ask for, and applications often cache on their own. A Java process, for example, caches successful lookups according to networkaddress.cache.ttl, regardless of the record's TTL, and connection pools that never reconnect never look the name up again at all. This is the main reason DNS makes a slow failover mechanism: moving traffic by changing a record takes at least the TTL, and some clients take much longer.
UDP, TCP and message size
A DNS message has a fixed 12-byte header followed by four sections: the question, the answer, the authority section (used for referrals) and the additional section (glue and other extras). The header holds the 16-bit ID that matches a response to its query, the flags seen above (QR, AA, TC, RD, RA and others), a response code such as NOERROR, NXDOMAIN or SERVFAIL, and the number of records in each section.
Most queries and answers fit in one UDP packet each, which is why DNS uses UDP: no connection setup, one round trip. RFC 1035 limited UDP messages to 512 bytes. EDNS(0) (RFC 6891) lets a client advertise that it can accept larger UDP responses, which DNSSEC signatures need. Very large UDP packets get fragmented, and fragments are often dropped or can be spoofed, so since DNS Flag Day 2020 the recommended size to advertise is 1,232 bytes.
If an answer does not fit, the server sends what it can with the TC (truncated) flag set, and the client repeats the query over TCP. TCP support is required of every DNS server (RFC 7766), and a firewall that allows only UDP on port 53 will break lookups for names with large answers. TCP is also used for zone transfers (AXFR and IXFR), which copy a zone from a primary server to its secondaries.
How the root servers handle the load
There are 13 root server identities, a to m, run by 12 organisations (Verisign runs two). The number comes from the original 512-byte limit: thirteen names and their addresses were what fitted in one UDP response. Each identity is not one machine. It is served from many sites around the world, together well over a thousand, using anycast: every site announces the same IP address in BGP, and the internet's routing sends each packet to the nearest site in routing terms.
Anycast suits DNS well. A query and its answer are a single exchange, so it does not matter if the next query goes to a different site. If a site fails, its route is withdrawn and traffic flows to the next nearest one, and a flood of traffic aimed at one address is spread across every site announcing it. Public resolvers such as 1.1.1.1, 8.8.8.8 and 9.9.9.9 use anycast for the same reasons, and so do most large authoritative DNS providers.
The root zone itself is small, a list of TLDs and their servers, and is published by IANA. Some resolvers keep a full local copy of it (RFC 8806) so they never need to query a root server for a referral at all.
DNSSEC and encrypted DNS
Original DNS has no protection. Queries and answers are plain text, and a resolver accepts the first response that matches its question, ID and port. In 2008 Dan Kaminsky showed that an attacker could race the real server with forged referrals and poison a resolver's cache for a whole domain. The immediate fix was to randomise the source port of every query, so a forger has to guess about 32 bits instead of 16, but that only makes the attack harder. Two separate additions deal with the underlying problems.
DNSSEC: proving the answer is genuine
DNSSEC adds signatures to DNS data. Each signed zone publishes its public keys in DNSKEY records and a signature for each set of records in RRSIG records. The parent zone publishes a DS record, a hash of the child's key, and signs that too. A validating resolver starts from the root's key, which it has built in, and follows the chain: the root signs the DS for com, com signs the DS for example.com, and example.com's key signs the A record. The root zone has been signed since July 2010.
DNSSEC proves that an answer came from the zone owner and was not changed. It does not hide anything: the query and the answer are still readable by anyone on the path. When validation fails, for example because a key was rolled badly or a DS record at the registrar no longer matches, a validating resolver returns SERVFAIL and the domain disappears for its users, which is the most common DNSSEC outage.
Encrypting the query
Encrypted DNS protects the link between a client and its resolver from being read or changed. Three forms are in use:
- DNS over TLS (DoT, RFC 7858) sends DNS messages over a TLS connection on port 853. Android's "Private DNS" setting uses it.
- DNS over HTTPS (DoH, RFC 8484) sends each query as an HTTPS request on port 443. It looks like other web traffic and is hard to block, and browsers can use it directly, bypassing the operating system's resolver, which some network operators object to.
- DNS over QUIC (DoQ, RFC 9250) uses QUIC on UDP port 853, avoiding TCP's head-of-line blocking.
These cover only the hop to the resolver. The resolver itself still sees every name, and its queries to authoritative servers are mostly unencrypted. DNSSEC and encrypted transport solve different problems and are used together.
Debugging DNS with dig
dig, from the BIND tools, is the standard way to see what DNS is returning. A few forms cover most problems.
dig www.example.com # ask your configured resolver for the A record dig www.example.com AAAA +short # just the IPv6 addresses dig @1.1.1.1 www.example.com # ask a specific resolver dig @ns1.example.com www.example.com +norecurse # ask the authoritative server directly dig +trace www.example.com # do the whole lookup yourself, from the root down dig -x 192.0.2.10 # reverse lookup (PTR)
dig +trace repeats the walk in steps 2 to 4 without using your resolver's cache, printing each referral and which server gave it. Its output (shortened here) shows where a lookup goes wrong:
. 518400 IN NS a.root-servers.net. ... ;; Received 525 bytes from 127.0.0.53#53(127.0.0.53) in 1 ms com. 172800 IN NS a.gtld-servers.net. ... ;; Received 1171 bytes from 198.41.0.4#53(a.root-servers.net) in 12 ms example.com. 172800 IN NS ns1.example.com. example.com. 172800 IN NS ns2.example.com. ;; Received 112 bytes from 192.5.6.30#53(a.gtld-servers.net) in 20 ms www.example.com. 3600 IN A 192.0.2.10 ;; Received 60 bytes from 192.0.2.53#53(ns1.example.com) in 31 ms
Some ways to read the results:
- The authoritative server has the new record but your resolver returns the old one. The old record is cached. The TTL in your resolver's answer is how many seconds are left.
- +trace stops at the TLD step or gets no answer from the domain's nameservers. The NS records at the registrar point to the wrong servers, the glue is out of date, or the nameservers are down.
- SERVFAIL from your resolver but an answer from dig +cd. The +cd flag turns off DNSSEC checking, so this means validation is failing. Check the DS record at the registrar against the zone's current DNSKEY.
- Two nameservers give different answers. A secondary has not picked up the latest zone. Compare the serial numbers in their SOA records.
- dig works but the application fails. dig ignores /etc/hosts and the operating system's cache. Try getent hosts www.example.com on Linux or dscacheutil -q host -a name www.example.com on macOS, which go through the same path as applications.
Common mistakes
- Changing a record without lowering its TTL first. Users keep reaching the old address for up to a full TTL, and a rollback takes just as long.
- Testing a name before creating it. The NXDOMAIN gets cached for the negative-caching TTL, and the new record seems not to work.
- Putting a CNAME at the bare domain, or next to other records. It is not allowed, and servers either reject it or behave unpredictably. Use the provider's ALIAS or flattening feature.
- Relying on DNS for fast failover. Even with a 30-second TTL, some resolvers and long-running clients hold on to the old address for minutes or longer. Put failover behind a stable address, such as a load balancer or anycast IP, where it can happen in seconds.
- Ignoring search domains. Kubernetes pods get options ndots:5 in resolv.conf, so a name with fewer than five dots, such as api.example.com, is first tried with each cluster search domain appended, producing several failed queries before the real one. Writing the name with a final dot, api.example.com., skips the search list.
- Letting a DNSSEC key or DS record get out of step. The domain becomes unreachable for every user of a validating resolver, which today includes the large public resolvers. Automate key rollover and check the DS record whenever the DNS provider changes.
Further reading
- RFC 1034, 1987Domain Names: Concepts and FacilitiesThe design: the namespace, zones, delegation and how resolvers work.
- RFC 1035, 1987Domain Names: Implementation and SpecificationThe message format, record types and zone file syntax.
- RFC 9499, 2024DNS TerminologyDefinitions of the terms used across the DNS standards.
- RFC 9156, 2021DNS Query Name Minimisation to Improve PrivacyHow resolvers avoid sending the full name to every server.
- RFC 4033, 2005DNS Security Introduction and RequirementsThe overview of DNSSEC, with RFC 4034 and 4035 for the details.
- Root server operatorsroot-servers.orgThe 13 identities, their operators and a map of every anycast site.
- SemicolonyDNS resolution simulatorRun lookups through each cache and server and change the TTLs.