is it dns? / wiki ← back to the live monitor

๐Ÿ” Query types: recursive, iterative, forwarding

Most DNS conversations on a real network are recursive: a client asks one resolver one question and gets one answer. But "recursive" is a role the resolver plays, not a kind of packet on the wire. That distinction explains why forwarders and conditional forwarding behave the way they do.

The three things a resolver can do with your query#

When a client sends dig example.com A to a resolver, the resolver can respond in one of three ways:

  1. Answer from cache. Already knows. Returns immediately. (Most queries.)
  2. Recurse. Walks the hierarchy itself (root โ†’ TLD โ†’ authoritative), assembles the answer, returns it, caches it.
  3. Forward. Hands the question to a different resolver (an "upstream") and waits for that server to do the work.

Cases 2 and 3 are both "recursive" from the client's perspective: the client sets the RD flag (Recursion Desired = 1) and gets a single answer back. What happened upstream is the resolver's business.

Key rule: recursion implies forwarding#

If a resolver is configured to recurse, every query it can't answer from cache will leave its interface heading somewhere: either to the roots, or to a configured upstream. Recursion implies forwarding.

This is the rule of thumb to keep at hand. The corollary: if recursion is off, your "forwarders" list does nothing. An authoritative-only server with recursion disabled treats its forwarders configuration as inert.

That sounds obvious until you've spent twenty minutes debugging "the forwarder isn't working" only to discover recursion got toggled off. Common across home routers, commercial appliances, and managed DNS services alike.

Three query flavors on the wire#

Recursive query (RD=1)#

client --RD=1--> resolver --...do the work...--> resolver --answer--> client

The client trusts the resolver to do whatever it needs to do. The client never speaks to the authoritative server directly: that is the resolver's job. Every stub resolver sets RD by default; the resolver does the walking.

dig example.com A
nslookup -type=A example.com
Resolve-DnsName example.com -Type A

Iterative query (RD=0)#

client --RD=0--> resolver
                 returns whatever it has, with referrals if needed
                 client itself walks the next hop

Used between resolvers and authoritative servers. A recursive resolver iterates on the client's behalf: first asking a root server, then the TLD, then the zone's authoritative servers, until it has a final answer.

Use +trace to watch this happen; dig does the iterating manually, printing each referral step.

dig +trace example.com A
.                       518400  IN  NS  a.root-servers.net.
;; Received 811 bytes from 127.0.0.53#53(127.0.0.53) in 1 ms

com.                    172800  IN  NS  a.gtld-servers.net.
;; Received 1176 bytes from 198.41.0.4#53(a.root-servers.net) in 12 ms

example.com.            172800  IN  NS  hera.ns.cloudflare.com.
example.com.            172800  IN  NS  elliott.ns.cloudflare.com.
;; Received 309 bytes from 192.5.6.30#53(a.gtld-servers.net) in 20 ms

example.com.            300     IN  A   104.20.23.154
example.com.            300     IN  A   172.66.147.243
;; Received 88 bytes from 173.245.58.162#53(hera.ns.cloudflare.com) in 9 ms

Each block is one referral hop, abbreviated to one line per level. The first block is dig priming itself: it asks your normal resolver (here systemd-resolved on 127.0.0.53) for the root server list, then walks everything else on its own. The Received ... from line under each block names the server that sent that block, so the com. referral arrives from a root server and the example.com. referral from a .com gTLD server. From there dig does the iterating itself (root โ†’ TLD โ†’ authoritative) exactly as a recursive resolver would, but printed step by step. Real output also includes RRSIG and DS records at each hop, because +trace turns on +dnssec automatically ("+dnssec is also set when +trace is set", BIND 9 dig manual). Every value here varies by run: which root and TLD instances dig happens to hit, the byte counts, and the timings. example.com is hosted on Cloudflare today, so its authoritative servers (hera / elliott.ns.cloudflare.com) and answer addresses look different from a name hosted elsewhere.

nslookup has no +trace equivalent. PowerShell can do the lookup but not the walk:

Resolve-DnsName example.com -Type A

+norec on dig sets RD=0, useful for poking an authoritative server directly (no recursion, answer from owned zones only). The server that owns the zone returns aa (authoritative answer) and NOERROR; a server that does not own the zone either refers or refuses. Cloudflare's nameservers refuse: ask coleman.ns.cloudflare.com for a zone Cloudflare does not host and you get REFUSED with no aa, plus an extended error explaining why (EDE 20, "Not Authoritative", RFC 8914).

Demo status: the probe.isitdns.net demo server is being rebuilt, so these specific lookups may not answer right now. The mechanics shown here are unchanged; the transcripts below are what a healthy exchange looks like.

Here pns1.isitdns.net is the authoritative server for probe.isitdns.net, so it answers directly:

dig @pns1.isitdns.net probe.isitdns.net SOA +norec
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
probe.isitdns.net. 300 IN SOA pns1.isitdns.net. hostmaster.isitdns.net. \
    2026061511 7200 3600 1209600 300

aa is set because pns1 owns the zone. rd is absent because the client sent RD=0, and the server correctly leaves it unset in the response.

nslookup -norecurse -type=SOA probe.isitdns.net pns1.isitdns.net

Network intercept caveat: on a network that intercepts port 53 (captive portals, some home routers), a @server query is answered by the interceptor instead of the named server. You may see only rd ra without aa; that is your network, not the server.

Cloudflare's coleman or hera work too. An authoritative server answers RD=0 queries for the zones it owns with aa set (RFC 1034 section 4.3.2); RD=0 is exactly what every recursive resolver sends it, so dig @coleman.ns.cloudflare.com isitdns.net SOA +norec returns a clean aa NOERROR just like the pns1 transcript above. Cloudflare's anti-amplification measure lives elsewhere: minimal responses to ANY queries (RFC 8482), not refusing RD=0. A self-hosted BIND, Knot, NSD, or CoreDNS instance works for the demo as well.

Forwarding#

client --RD=1--> local resolver --RD=1--> upstream resolver --answer--> ...

The local resolver doesn't iterate: it just passes the buck. This is what most enterprise resolvers actually do: forward to a curated upstream (Cloudflare, Quad9, a corporate Internet-facing recursive cluster) rather than walking from the root themselves. See forward for how forward zones are configured.

Why forward instead of recurse to root?

  • Cache locality. The upstream has a fatter, hotter cache.
  • Egress control. Easier to monitor and filter one egress point than every resolver in the org.
  • Policy/RPZ. The upstream applies threat-intel filtering you don't want to duplicate.
  • DNSSEC offload. Some clients delegate validation to a trusted upstream (see dnssec for the trade-offs).

It's common to chain forwarders: client โ†’ local resolver โ†’ org-level resolver โ†’ roots. Each hop honors RD=1; the last hop iterates.

Conditional forwarding#

You can forward only some queries based on the queried name. Useful in multi-DNS environments where different parent zones live on different authoritatives.

PatternExample
Forward *.internal.lan to an internal DNSA subsidiary's namespace lives on another team's resolvers
Forward *.consul to a service discovery resolverHashicorp Consul, a Kubernetes cluster's internal resolver
Forward in-addr.arpa for a partner's IP spaceReverse lookups for a connected customer

Most recursive resolvers expose this under names like "Forward Zones," "Stub Zones," or "Conditional Forwarders." Pure authoritative services (the cloud-DNS providers) don't expose conditional forwarding because it's an upstream-feature concept. See delegations for how zone authority is partitioned across servers.

What you'll see in dig flags#

After every dig answer, the flags line tells you what just happened:

;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
FlagMeaningWhen set
qrThis is a response, not a queryAlways on answers
rdRecursion was desiredAlmost always: client wants the resolver to do the work. Servers echo it back in the response even when they will not recurse, so rd without ra from an authoritative server is normal
raRecursion is availableResolver is willing and able to recurse: off on authoritative-only servers
aaAuthoritative answerThe responder owns the zone (rare for typical dig output: you'll see this when you @<authoritative-server>)
adAuthenticated dataSet by the resolver after it validates the DNSSEC chain. Requesting +dnssec sets the DO bit (asks for RRSIGs) but does NOT guarantee ad; the resolver sets ad only when it validates. Can be stripped on the path. See dnssec.
cdChecking disabledClient asked the resolver to skip DNSSEC validation

A handy check: query a recursive resolver and confirm ra is set.

dig @1.1.1.1 example.com A
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
nslookup -type=A example.com 1.1.1.1

dig shows ra in the flags line; nslookup confirms the answer resolves but cannot display flags (use dig or a DoH client for ra). Do not read anything into the missing ad here: example.com is DNSSEC-signed and 1.1.1.1 validates it, but 1.1.1.1 only sets ad when the query signals DNSSEC interest (add +dnssec and ad appears), while 8.8.8.8 sets ad on the plain query. Both behaviors are allowed (RFC 6840 section 5.8). In RFC terms, an authoritative-only server should leave ra unset to signal it will not recurse, and the big managed authoritative providers (Cloudflare, Route 53, NS1) do exactly that: query one of their nameservers directly and ra is absent. So ra=0 is easy to observe in the wild: any large provider's authoritative nameserver shows it, as does a self-hosted authoritative server (BIND, NSD, or Knot) with recursion explicitly disabled. If you query an authoritative server and see ra set anyway, suspect a port-53 interceptor on your own network answering in its place.

What "forwarders enabled but empty" looks like#

A classic misconfiguration across home routers, commercial appliances, and open-source DNS servers alike:

  • Recursion is on.
  • Forwarders are enabled as a feature.
  • The forwarders list itself is empty.

The resolver silently iterates from root for everything. Sometimes that's what someone wanted; often not. Audit dig +trace from the resolver itself if you're not sure which path queries are actually taking.

See also#