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

➡️ Forward zones

A forward zone says: "For any name inside this zone, don't recurse from root and don't claim authority: hand the query to a specific upstream resolver and wait for its answer." Forward zones are how your resolver participates in someone else's namespace without owning it.

What it is#

A forward zone is a conditional pointer. When a query arrives matching the zone's name, the local resolver:

  1. Notices the match
  2. Forwards the query, recursion desired (rd=1), to the configured forwarders (one or more upstream IPs)
  3. Waits for the answer
  4. Returns it to the client and caches it locally

Because the forwarded query asks for recursion, the forwarder must itself be a recursive resolver, or be authoritative for the zone. Pointing a forward zone at an authoritative-only server that does not host the zone gets you REFUSED.

The response your resolver hands back carries aa=0 because your server is not authoritative: it just acted as a proxy. The aa bit is reserved for servers that are an authority for the queried name (RFC 1035 §4.1.1).

This differs from a delegation (you publish NS pointing downstream: resolvers follow the referral there directly) and from an auth zone (you have the data yourself).

When to use it#

  • A partner's internal namespace: your resolver should chase internal.partner.example to their DNS, never to public roots.
  • Service discovery domains: *.consul, *.cluster.local: forward to the discovery resolver.
  • Reverse zones for an upstream's IP space: when a partner delegates a /26 to you over a VPN, forward the matching .in-addr.arpa zone to their DNS.
  • Migration windows: temporarily forward an old zone to its new authoritative location during a cutover.
  • split-horizon namespaces: return different answers to internal vs external clients by forwarding the internal view to a private resolver.

When not to use it#

  • When you own the records: use auth.
  • When you want clients to land directly on the target without going through your resolver: use delegations.
  • When you just want NS-record caching for faster referrals: use stub.
  • When you need encrypted transport to the upstream resolver: see dot-doh for DoT and DoH forwarder options.

Real-world examples#

Use caseForward zoneForwarders
Partner intranet visible only via VPNinternal.partner.example.their internal DNS IPs
HashiCorp Consul service discoveryconsul.the local Consul agent's DNS
Kubernetes cluster namescluster.local.the cluster's internal resolver
Reverse for partner-delegated /26<oct>.<oct>.<oct>.in-addr.arpa.their DNS IPs

How to configure#

Every recursive DNS server expresses this with the same conceptual fields: the zone name, the forwarder IPs, and a "forwarders-only" flag. The exact syntax varies by server, so consult your DNS server's documentation; every common implementation supports it. See nslookup-and-dig for the client-side tools you will use to verify a forward zone is working.

The "forwarders-only" flag: the load-bearing one#

Whether a resolver falls back to full recursion when the forwarders fail depends on the implementation, and the defaults disagree. BIND defaults to forward first: query the forwarders, and if that does not answer the question, resolve it yourself (BIND 9 ARM, Forwarding). Unbound defaults the other way: forward-first is no, so a failed forward stays failed (unbound.conf(5)).

Fallback is often the wrong behavior for an internal namespace: if the forwarders for internal.partner.example are down, you probably want SERVFAIL, not for your resolver to ask root nameservers about internal.partner.example (where it does not exist) and cache an NXDOMAIN.

Setting "forwarders only" (BIND: forward only;, Unbound: leave forward-first at its default no) says: only use these forwarders. Fail closed with SERVFAIL. Set it explicitly on every internal-namespace forward zone rather than trusting your server's default.

Wire-level: what queries look like#

The commands below send a query to the local resolver. The resolver then forwards that query to the configured forwarder IP (port 53 by default; port 853 for a DoT forwarder, RFC 7858 §3.1; HTTPS for a DoH forwarder, RFC 8484) and returns the answer with aa=0. Only a packet capture or resolver debug log shows the forwarder-side traffic; the client tool does not.

Replace <resolver> with your internal resolver's IP address.

dig @<resolver> service.internal.partner.example A
nslookup -type=A service.internal.partner.example <resolver>

To verify a forward is working, query a forwarded name and check that aa is absent from the flags line:

dig @<resolver> service.internal.partner.example +nostats +noquestion

The aa flag is absent because the local resolver is not authoritative: it forwarded the query. Even when the forward target is authoritative, the forwarding resolver strips aa before handing the answer back, so the client always sees aa=0 on a working forward. If aa=1 shows up at the client, the local server itself is authoritative: your zone classification is wrong and you have actually created an auth zone.

nslookup never prints the DNS header flags line; its one hint is the Non-authoritative answer: label, printed whenever the answer has aa=0. Use dig for flag inspection. See nslookup-and-dig for a full comparison.

Gotchas#

  • Loop hazard. Forward A to B to A is a recursion loop. Resolvers return SERVFAIL after retry, but you will see latency spikes and log noise. Audit chains.
  • Fallback to root. On servers whose default allows it (BIND's forward first), a forward zone leaks queries to root nameservers when the target is down. Almost always wrong for internal namespaces; set "forwarders only" explicitly.
  • TLS-aware forwarders. If forwarding to a DoT-capable upstream, many modern resolvers support configuring TLS on the forwarder entry (BIND attaches a tls transport to the forwarder address; Unbound sets forward-tls-upstream: yes). Plain UDP/53 is the default. Choose based on the path.
  • The forwarder list is redundancy, not load-balancing. Selection is implementation-specific: some servers walk the list in order, others prefer whichever forwarder has answered fastest. Treat multiple forwarders as failover, and check your server's documentation before assuming an order.
  • Wildcards are not supported. You forward an entire zone: *.internal.partner.example is not a valid forward zone name. Forward internal.partner.example and everything under it follows.
  • DNSSEC validation. Two distinct failure modes. If the forwarded zone is signed, the forwarder must be DNSSEC-aware and return RRSIG records when asked (a validating resolver sets the DO bit on its upstream queries itself); a forwarder that strips them turns every answer into SERVFAIL. If the forwarded zone is a private, unsigned namespace like consul. or cluster.local., validation fails differently: the signed public tree proves those names do not exist, so answers for them are judged bogus. Carve the name out of validation for that zone only (BIND: validate-except; Unbound: domain-insecure). See dnssec and dnssec-troubleshooting.

See also#