Tue, July 1, 2025 Β· 6 min read

DNS Demystified 2: Recursive and Non-Recursive Resolvers

In the previous article, we traced the lookup chain from application to root server. Now we focus on the component that does the walking: the resolver.

What is a Resolver?

A resolver is a piece of software that translates a domain name into an IP address. There are two broad categories:

  • Stub resolver β€” a minimal client embedded in the OS or application library
  • Recursive resolver β€” a full server that walks the DNS hierarchy on behalf of clients

The split between stub and recursive resolvers is described in RFC 1034 section 5.3.1 and 5.3.2. This separation of concerns is what makes DNS scalable β€” millions of stub resolvers delegate to a much smaller number of recursive resolvers.

Reference: RFC 1034 β€” Domain Names - Concepts and Facilities, Section 5.3. https://datatracker.ietf.org/doc/rfc1034/

Stub Resolvers

Every operating system ships with a stub resolver. On Linux, it is part of glibc (man 3 gethostbyname, man 3 getaddrinfo). On Windows, it lives in the networking stack (DnsQuery API).

The stub resolver:

  • Reads /etc/resolv.conf (man 5 resolv.conf) to find recursive resolvers
  • Sends queries to those resolvers and waits for answers
  • Maintains a small local cache (if nscd or systemd-resolved is running)
  • Does not walk the DNS hierarchy itself

Configuration in /etc/resolv.conf:

nameserver 127.0.0.53
nameserver 8.8.8.8
options rotate
options timeout:2
options attempts:3
  • rotate: Cycle through nameservers round-robin for load distribution.
  • timeout:2: Wait 2 seconds before trying the next nameserver.
  • attempts:3: Retry each nameserver up to 3 times before giving up.

Pro tip: The order of nameserver lines matters. The resolver tries the first one. If it times out, it moves to the second. The rotate option changes this to round-robin. On modern systemd systems, /etc/resolv.conf is often managed by systemd-resolved (see man 8 systemd-resolved) and points to 127.0.0.53.

Real-world context: A misconfigured /etc/resolv.conf is one of the most common causes of β€œinternet is slow” tickets. If the first nameserver is unreachable or slow, every DNS query blocks for the full timeout duration before falling back. Check yours with:

cat /etc/resolv.conf
resolvectl status   # if using systemd-resolved

Recursive Resolvers

A recursive resolver (also called a recursive nameserver or caching resolver) accepts queries from stub resolvers and does the full resolution work as defined by the algorithm in RFC 1034 section 4.3:

  1. Checks its cache for a valid answer
  2. If not cached, queries the root servers
  3. Follows referrals down the hierarchy
  4. Returns the final answer to the stub resolver
  5. Caches the result for the duration of the TTL

The resolution algorithm in RFC 1034 section 4.3.2 specifies exact steps: check local data, query NS records, follow referrals, iterate until authoritative. This is the algorithm every compliant recursive resolver implements.

Public recursive resolvers:

ProviderIPv4IPv6PrivacyDNSSECNotes
Cloudflare1.1.1.12606:4700:4700::1111No loggingYesFastest global anycast, supports DNS-over-TLS/HTTPS
Google8.8.8.82001:4860:4860::8888Logs anonymisedYesMost widely deployed, huge cacheε‘½δΈ­ηŽ‡
Quad99.9.9.92620:fe::feNo loggingYesBlocks known malicious domains by default
OpenDNS208.67.222.2222620:119:35::35Logs (configurable)YesContent filtering categories available

Reference: Cloudflare DNS docs β€” https://developers.cloudflare.com/1.1.1.1/ Reference: Quad9 threat intelligence β€” https://www.quad9.net/

Running a local recursive resolver (Unbound, BIND in recursive mode) improves privacy and reduces latency because all your LAN queries share a single cache.

Recursive vs Non-Recursive (Iterative) Queries

These terms describe how a resolver handles a query. The distinction is defined by the RD (Recursion Desired) bit in the DNS query header (RFC 1035 section 4.1.1).

Recursive Query

The client asks the resolver: β€œGive me the answer, do the work for me.”

The resolver must either return a definitive answer (A record, NXDOMAIN, SERVFAIL) or an error. The resolver handles all follow-up queries. The RD bit is set to 1.

Stub: "What is the A record for rootlog.in?"
Recursive resolver: walks root β†’ TLD β†’ authoritative
Recursive resolver: "It is 185.199.108.153"

Pro tip: When a recursive resolver returns a response, check the RA (Recursion Available) flag in the DNS header. If RA is set, the server supports recursion for this client. If RA is absent, the server is authoritative-only or recursion is restricted:

dig @8.8.8.8 rootlog.in +nocmd +nocomments | grep "flags:"

Non-Recursive (Iterative) Query

The client asks the server: β€œGive me the best answer you have right now.”

The server responds with whatever it knows β€” either a cached answer or a referral to another server. The client must follow referrals itself. The RD bit is set to 0.

Stub: "What is the A record for rootlog.in?"
Recursive resolver (with RD=0): "I don't know, but ask a.root-servers.net."
Stub: queries a.root-servers.net
Root (with RD=0): "I don't know, but ask ns1.registry.in."
...

Recursive resolvers use iterative queries internally when walking the chain. The +trace option in dig sends iterative queries (RD=0) and prints each step.

Real-world context: Authoritative-only servers (like your BIND installation from Article 6) should always have recursion disabled. If an authoritative server answers recursive queries, it can be used as an amplifier in DNS amplification DDoS attacks β€” a small query of ~60 bytes can produce a response of ~4000 bytes, a 60x amplification factor.

Flow Comparison

Recursive query (RD=1):
Computer ──[one query]──> 8.8.8.8 ──[iterative walk]──> root β†’ TLD β†’ NS
Computer <──[one answer]── 8.8.8.8

Iterative query (RD=0):
Computer ──> root ──referral──> Computer
Computer ──> TLD ──referral──> Computer
Computer ──> NS ───answer────> Computer

Testing Recursion

Check if a server allows recursion:

dig @8.8.8.8 rootlog.in +recurse

The +recurse flag sets RD=1. Compare with:

dig @8.8.8.8 rootlog.in +norecurse

With +norecurse, Google will only return a cached answer or a referral β€” not walk the chain for you.

Benchmarking Resolver Performance

Use a timed loop to compare resolver speed:

for r in 1.1.1.1 8.8.8.8 9.9.9.9; do
    echo -n "Resolver $r: "
    time (dig @"$r" rootlog.in +short > /dev/null 2>&1)
done 2>&1 | grep -E "(Resolver|real)"

Run this multiple times. The first query to a resolver includes the full recursive walk; subsequent queries hit the cache and should be sub-millisecond.

Pro tip: When troubleshooting slow DNS, always check query time with dig +trace. Each delegation step prints in milliseconds:

rootlog.in.              3600    IN      NS      dns1.p01.nsone.net.
rootlog.in.              3600    IN      NS      dns2.p01.nsone.net.
;; Received 123 bytes from 193.0.14.129#53(k.root-servers.net) in 14 ms
;; Received 87 bytes from 156.154.73.10#53(ns2.p01.nsone.net) in 72 ms

The first hop (root β†’ TLD) takes 14 ms. The second hop (TLD β†’ NS) takes 72 ms β€” that is the slow link.

How systemd-resolved Works

Most modern Linux systems run systemd-resolved (man 8 systemd-resolved). It provides:

  • A stub resolver on 127.0.0.53
  • DNSSEC validation (see resolvectl dnssec)
  • DNS-over-TLS support (see resolvectl tls)
  • Split DNS β€” different resolvers for different domains (see resolvectl domain)
  • Local caching

Check its status:

resolvectl status
resolvectl query rootlog.in
resolvectl statistics

Pro tip: systemd-resolved maintains its own cache separate from the application or recursive resolver. If you change a DNS record but your system still resolves the old IP, flush the resolved cache:

resolvectl flush-caches

Reference: man 8 systemd-resolved β€” the full systemd-resolved man page. Reference: man 5 resolved.conf β€” configuration file syntax.

Key Takeaways

  • Stub resolvers (man 3 gethostbyname) delegate to recursive resolvers via /etc/resolv.conf (man 5 resolv.conf).
  • Recursive resolvers implement the RFC 1034 section 4.3 algorithm: iterate referrals until authoritative answer.
  • Recursive queries (RD=1) ask the resolver to do the work; iterative queries (RD=0) return referrals.
  • The RA (Recursion Available) flag in responses tells you whether a server accepts recursive queries.
  • Running a local recursive resolver (Unbound) improves privacy and LAN-wide cacheε‘½δΈ­ηŽ‡.
  • systemd-resolved (man 8 systemd-resolved) adds a caching stub on 127.0.0.53 with DNSSEC and DoT support.
  • Benchmark resolvers with timed dig loops; use dig +trace to identify slow delegation hops.
  • Keep /etc/resolv.conf clean β€” one slow or unreachable nameserver blocks resolution for the full timeout.

In the next article, we will cover DNS zones, record types, and how to query them with dig, host, and nslookup.