| |

LFCE 37 ๐Ÿง BIND9 โ€” Forwarding and Recursion

โฑ๏ธ Estimated read: 11 min | |

Forwarding and recursion are two mechanisms BIND9 uses to resolve names it is not authoritative for. Recursion is the iterative process: the server starts at the root, follows referrals down the hierarchy, and caches the result. Forwarding is a shortcut: the server sends the query to an upstream resolver and waits for the answer, instead of performing the iterative lookups itself. The two are related but distinct. A server can recurse without forwarding, forward without recursing (in forward only mode), or use a combination of both (in forward first mode). The configuration determines which mechanism is used and when.

The previous chapter covered authoritative zones and records. This chapter covers the resolver side of BIND9: the recursion directive that enables iterative resolution, the forwarders list that specifies upstream servers, the forward directive that controls fallback behavior, and the security boundaries (allow-recursion, allow-query-cache) that prevent the server from becoming an open resolver.

Key point: forward only and forward first are not the same as recursion. forward only disables iterative resolution entirely; if the forwarder does not answer, the query fails. forward first tries the forwarder first, and if the forwarder does not respond, the server falls back to iterative recursion. The forward directive has no effect unless forwarders is non-empty, and turning off recursion also turns off forwarding .


Why forwarding and recursion are separate controls

The iterative-resolution problem.

Recursion is the default behavior of a resolver. When a client asks for www.example.com and the server does not have it cached, the server queries a root server, receives a referral to the .com TLD servers, queries one of those, receives a referral to the example.com authoritative servers, and queries one of those for the final answer. This process is iterative: the server does the work of following the delegation chain . It requires outbound access to the root servers and the ability to reach arbitrary authoritative servers on port 53.

The forwarding problem.

Some network environments restrict outbound DNS traffic. A server behind a firewall may be allowed to query only a specific upstream resolver, not the root servers or arbitrary authoritative servers. Forwarding solves this by sending all recursive queries to the designated upstream, which performs the iterative resolution on the server’s behalf. The local server caches the answers, but it does not perform the lookups itself. This is useful when the network policy requires all DNS traffic to pass through a controlled egress point.

The fallback problem.

forward first is the default when forwarders is configured . It attempts the forwarder first. If the forwarder responds, the answer is used. If the forwarder does not respond within a timeout, the server falls back to iterative recursion. This provides resilience: if the forwarder is down, the server can still resolve names, provided it has outbound access. forward only removes this fallback. The server never attempts iterative resolution; if the forwarder fails, the query fails . The choice between the two depends on whether the server is allowed to perform iterative lookups.


a. Enabling recursion and restricting it

Recursion is enabled with recursion yes; in the options block. It is the default in BIND9, but it must be explicitly restricted to prevent the server from being used as an open resolver.

# Enable recursion and restrict it to trusted clients
options {
    directory "/var/cache/bind";
    recursion yes;                         # Enable iterative resolution
    allow-recursion { trusted; };          # Only trusted clients may recurse
    allow-query-cache { trusted; };        # Only trusted clients may read the cache
};

acl "trusted" {
    192.168.1.0/24;                        # Local network
    127.0.0.1;                             # Loopback
    ::1;
};

The allow-recursion ACL is the primary security boundary. Without it, the server accepts recursive queries from any client, which makes it an amplification vector . The allow-query-cache ACL controls who can read cached answers. If it is not set, BIND falls back to allow-recursion; if that is also not set, the default is localnets and localhost . The two should be configured together so that the same set of clients can both trigger recursive lookups and read the cached results.


b. Configuring forwarders and forward mode

Forwarding is configured with two statements: forwarders specifies the upstream servers, and forward specifies the fallback behavior.

# Forward all recursive queries to upstream resolvers
options {
    directory "/var/cache/bind";
    recursion yes;                         # Required for forwarding to work
    allow-recursion { trusted; };
    forwarders {
        8.8.8.8;                           # Google Public DNS
        8.8.4.4;
        1.1.1.1;                           # Cloudflare DNS
    };
    forward first;                         # Try forwarders first; fall back to recursion
};

The forwarders list can contain multiple addresses. BIND selects among them using an RTT-based algorithm, preferring the fastest responder . The forward directive has two values. first (the default) tries the forwarders, and if they fail to respond, falls back to iterative recursion. only queries only the forwarders and never performs iterative resolution . A server configured with forward only behaves like a stub resolver with a cache: it relies entirely on the upstream for resolution.

The recursion directive must remain enabled for forwarding to function. The BIND documentation and mailing list confirm that turning off recursion also turns off forwarding . A server with recursion no and a forwarders list will not forward queries; the forwarders are ignored.


c. Zone-level forwarding and the empty forwarders list

Forwarding can be configured per-zone, overriding the global settings. A zone statement of type forward applies forwarding only to queries within that zone.

# Forward only queries for partner.example.com to a specific server
zone "partner.example.com" {
    type forward;
    forward only;                          # Only use the forwarder for this zone
    forwarders { 10.0.0.53; };             # Internal resolver for the partner domain
};

# Disable forwarding for a specific zone (use recursion instead)
zone "internal.example.com" {
    type forward;
    forwarders { };                        # Empty list: no forwarding for this zone
};

The empty forwarders { }; statement is the mechanism for turning off forwarding for a specific zone when a global forwarder is configured. It tells BIND that queries for this zone should not be forwarded, even though the global forwarders list would otherwise apply . This is useful when the global forwarder is appropriate for most queries but not for a specific internal domain that must be resolved iteratively.


Complete Example Session

# ============================================
# PART 1: THE ACL FOR TRUSTED CLIENTS
# ============================================
# Define the trusted network before the options block
acl "trusted" {
    192.168.1.0/24;
    127.0.0.1;
    ::1;
};

# ============================================
# PART 2: ENABLE RECURSION WITH RESTRICTIONS
# ============================================
options {
    directory "/var/cache/bind";
    recursion yes;
    allow-recursion { trusted; };
    allow-query-cache { trusted; };
    allow-query { any; };                  # Authoritative queries from anyone
};

# ============================================
# PART 3: CONFIGURE FORWARDERS
# ============================================
options {
    forwarders {
        8.8.8.8;
        8.8.4.4;
    };
    forward first;                         # Try forwarders, fall back to recursion
};

# ============================================
# PART 4: VALIDATE THE CONFIGURATION
# ============================================
named-checkconf
# No output means the syntax is valid

# ============================================
# PART 5: RESTART AND TEST RECURSION
# ============================================
systemctl restart named
dig @localhost google.com
# Output: an answer with the "ra" flag (recursion available)

# ============================================
# PART 6: TEST FORWARDING IS ACTIVE
# ============================================
# Query a name that is not cached
dig @localhost example.org
# The answer comes from the forwarder or from recursion
# Check the query log to confirm the forwarder was used

# ============================================
# PART 7: TEST THE CACHE
# ============================================
dig @localhost google.com
# The second query returns faster because the answer is cached

# ============================================
# PART 8: TEST THE RESTRICTION
# ============================================
# From a client outside the trusted ACL, recursion should be refused
# dig @server google.com from an untrusted IP
# Output: status: REFUSED (or no answer)

# ============================================
# PART 9: SWITCH TO FORWARD ONLY
# ============================================
# Edit the options block
# forward only;
# Restart and test
systemctl restart named
dig @localhost google.com
# If the forwarder is reachable, the query succeeds
# If the forwarder is unreachable, the query fails (no recursion fallback)

# ============================================
# PART 10: VERIFY WITH rndc
# ============================================
rndc status
# Output shows the server is running
rndc dumpdb -cache
# Dumps the cache to a file for inspection

The ten parts demonstrate the complete forwarding and recursion configuration: ACL definition, recursion enablement with restrictions, forwarder configuration, validation, testing recursion, testing forwarding, testing the cache, testing the restriction, switching to forward only, and verification with rndc. The critical distinction is between Parts 5 and 9: with forward first, the server can still resolve names if the forwarder fails; with forward only, it cannot.


Quick Reference

Recursion and Forwarding Statements

StatementValuesPurpose
recursionyes / noEnable or disable iterative resolution
allow-recursionACLClients allowed to make recursive queries
allow-query-cacheACLClients allowed to read cached answers
forwardersIP listUpstream resolvers for forwarding
forwardfirst / onlyFallback behavior when forwarders fail

Forward Modes

ModeBehaviorFallback
firstTry forwarders, then recurseYes
onlyOnly forwarders, never recurseNo

Security Defaults

SettingDefaultRisk
recursionyesServer may be an open resolver
allow-recursionlocalnets; localhost;Local network only
allow-query-cacheFalls back to allow-recursionCache exposure

Best Practices

โœ… Do This:

# Restrict recursion to trusted clients
allow-recursion { trusted; };                              # โœ…

# Set allow-query-cache to match
allow-query-cache { trusted; };                            # โœ…

# Keep recursion enabled when using forwarders
recursion yes;                                             # โœ…

# Use forward first for resilience
forward first;                                             # โœ…

# Use an empty forwarders list to disable forwarding for a zone
zone "internal" { type forward; forwarders { }; };        # โœ…

# Validate before reloading
named-checkconf; rndc reload                               # โœ…

โŒ Don’t Do This:

# Leave recursion open to the internet
allow-recursion { any; };    # Amplification vector         # โŒ

# Set recursion no and expect forwarders to work
recursion no; forwarders { 8.8.8.8; };    # Ignored        # โŒ

# Use forward only without a reliable forwarder
forward only;    # No fallback if forwarder fails          # โŒ

# Omit the ACL for allow-recursion
# Default is localnets; may not include your client subnet # โŒ

# Assume forwarding overrides authoritative data
# Authoritative zones are answered locally, not forwarded  # โŒ

Common Pitfalls

PitfallWhy It HappensFix
Forwarders not usedrecursion no disables forwardingSet recursion yes
Recursion refused for local clientsACL does not include the subnetAdd the subnet to allow-recursion
Queries fail when forwarder is downforward only prevents fallbackUse forward first
Open resolverallow-recursion { any; }Restrict to trusted ACL
Zone not forwarded despite global forwardersZone has empty forwarders { };Remove the empty statement or add forwarders
Cache not usedallow-query-cache too restrictiveMatch it to allow-recursion

Real-World Examples

1. Enable recursion with ACL

options { recursion yes; allow-recursion { trusted; }; };

2. Configure forwarders

options { forwarders { 8.8.8.8; 1.1.1.1; }; forward first; };

3. Forward only mode

options { forwarders { 10.0.0.53; }; forward only; };

4. Empty forwarders for a zone

zone "internal" { type forward; forwarders { }; };

5. Zone-specific forwarder

zone "partner.com" { type forward; forward only; forwarders { 10.0.0.54; }; };

6. Trusted ACL

acl "trusted" { 192.168.1.0/24; 127.0.0.1; };

7. Validate configuration

named-checkconf

8. Test recursion

dig @localhost google.com

9. Check cache

rndc dumpdb -cache

10. Reload without restart

rndc reload

Visual

Recursion vs Forwarding

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  RECURSION: SERVER DOES THE WORK                             โ”‚
โ”‚                                                              โ”‚
โ”‚  Client โ”€โ”€โ–ถ BIND9 โ”€โ”€โ–ถ Root โ”€โ”€โ–ถ TLD โ”€โ”€โ–ถ Authoritative         โ”‚
โ”‚                    (iterative lookups, caches answers)       โ”‚
โ”‚                                                              โ”‚
โ”‚  Requires outbound access to root and authoritative servers. โ”‚
โ”‚                                                              โ”‚
โ”‚  FORWARDING: UPSTREAM DOES THE WORK                          โ”‚
โ”‚                                                              โ”‚
โ”‚  Client โ”€โ”€โ–ถ BIND9 โ”€โ”€โ–ถ Forwarder โ”€โ”€โ–ถ Answer                   โ”‚
โ”‚                    (single query to upstream, caches answer) โ”‚
โ”‚                                                              โ”‚
โ”‚  Requires outbound access only to the forwarder.             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Forward First vs Forward Only

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  FALLBACK BEHAVIOR                                           โ”‚
โ”‚                                                              โ”‚
โ”‚  forward first:                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 1. Send query to forwarder                             โ”‚  โ”‚
โ”‚  โ”‚ 2. If forwarder answers โ†’ return answer                โ”‚  โ”‚
โ”‚  โ”‚ 3. If forwarder times out โ†’ perform recursion          โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                              โ”‚
โ”‚  forward only:                                               โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 1. Send query to forwarder                             โ”‚  โ”‚
โ”‚  โ”‚ 2. If forwarder answers โ†’ return answer                โ”‚  โ”‚
โ”‚  โ”‚ 3. If forwarder times out โ†’ FAIL (no recursion)        โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                              โ”‚
โ”‚  first is resilient. only is dependent on the forwarder.     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Security Boundaries

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  TWO ACLs CONTROL ACCESS                                     โ”‚
โ”‚                                                              โ”‚
โ”‚  allow-recursion { trusted; };                               โ”‚
โ”‚  โ”œโ”€โ”€ Who can trigger recursive lookups                       โ”‚
โ”‚  โ””โ”€โ”€ Without it: open resolver                               โ”‚
โ”‚                                                              โ”‚
โ”‚  allow-query-cache { trusted; };                             โ”‚
โ”‚  โ”œโ”€โ”€ Who can read cached answers                             โ”‚
โ”‚  โ””โ”€โ”€ Without it: falls back to allow-recursion               โ”‚
โ”‚                                                              โ”‚
โ”‚  Both should match the same trusted set.                     โ”‚
โ”‚  Authoritative queries are controlled by allow-query.        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Recursion directiverecursion yes; (default)
Recursion ACLallow-recursion { trusted; };
Cache ACLallow-query-cache { trusted; };
Forwardersforwarders { ip; };
Forward modeforward first; (default) or forward only;
Forwarding requiresrecursion yes;
Empty forwardersforwarders { }; disables forwarding for a zone
Zone-level forwardingzone "name" { type forward; ... };
Default forward modefirst (fallback to recursion)
Security riskallow-recursion { any; }

Key takeaways:

  • Recursion and forwarding are separate mechanisms. Recursion performs iterative lookups from the root. Forwarding sends queries to an upstream resolver. The recursion directive must be enabled for forwarding to work .
  • forward first is resilient; forward only is dependent. first tries the forwarder and falls back to recursion if it fails. only never recurses; if the forwarder fails, the query fails .
  • allow-recursion is the security boundary. Without it, the server is an open resolver and an amplification vector. The default is localnets and localhost, which may not include your client subnet .
  • allow-query-cache should match allow-recursion. It controls who can read cached answers. If it is not set, it falls back to allow-recursion .
  • The empty forwarders { }; list disables forwarding for a zone. This overrides the global forwarders list and forces iterative recursion for that zone .
  • Authoritative data overrides forwarding. Queries for zones the server is authoritative for are answered locally, not forwarded. Forwarding applies only to names the server is not authoritative for .

Remember: Forwarding is a shortcut for recursion. The server sends the query upstream instead of doing the iterative work itself. forward first tries the shortcut and falls back to the full path; forward only relies entirely on the shortcut. The recursion directive must be enabled for either to work. The allow-recursion and allow-query-cache ACLs are the security boundaries that prevent the resolver from being abused. Configure the forwarders, choose the mode, restrict the clients, and validate before reloading.


Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!