OPNsenseXVIII · DNS and UnboundUnbound configuration
Unbound configuration — interfaces, ACLs, DNSSEC, prefetch, cache size
What you'll learn
- Configure the interfaces Unbound listens on for production segmentation
- Apply the access-list model so only the intended subnets can query the resolver
- Enable DNSSEC validation and explain what it does and does not prove
- Set message-cache and RRset-cache sizes for the query volume the firewall serves
- Recognise the hardening options that reduce the cache-poisoning attack surface
Prerequisites
Verified against OPNsense 25.x · FreeBSD 14.x · PF (FreeBSD packet filter) FreeBSD 14.x · Unbound 1.20+ · Kea DHCP OPNsense 25.x plugin · WireGuard in-kernel + OPNsense plugin · strongSwan (IPsec plugin) OPNsense 25.x plugin · OpenVPN 2.6.x · Suricata 7.x · 2026-08-14
A default OPNsense install ships with Unbound listening on every interface, accepting queries from every attached subnet, validating DNSSEC, caching the response, and serving the next client from cache. That is a workable starting point. It is not a production configuration. The production configuration answers five questions deliberately: which interfaces serve DNS, who can query, what the cache can hold, how DNSSEC validation behaves, and which hardening options reduce the attack surface.
This lesson covers the configuration knobs that turn the default into a production resolver and the operational discipline that keeps the configuration deliberate.
Which interfaces Unbound listens on
OPNsense can answer DNS on every interface the firewall owns. The default is to listen on all of them. The production choice is narrower: listen on the interfaces that should serve DNS to clients, and do not listen on interfaces that should not.
The two configurations to avoid:
Listening on the WAN. The default listens on every interface, including WAN. A resolver listening on the WAN answers recursive queries from the public Internet. An open recursive resolver is an amplifier — an attacker can use the firewall’s bandwidth and CPU to amplify a small query into a large response to a victim — and a fingerprint that the firewall is Unbound on FreeBSD. The production rule: never listen on WAN unless the operator has a specific reason, and even then with rate limiting.
Listening on management. The management interface is meant for administrative access, not for serving DNS to clients. Listening on management means every administrative network can be a client of the resolver. In some estates that is intentional (the management VLAN uses the firewall as its resolver because the firewall is the only thing the management hosts are allowed to talk to). In most it is an accident: the management interface is on the same physical port as a VLAN, the listening list is “all”, and now the management hosts have an open resolver.
The discipline: pick the interfaces explicitly. LAN, guest, DMZ if the DMZ needs to resolve through the firewall, the VLANs that should have the firewall as their resolver. Uncheck WAN and management unless there is a written reason.
Access lists: who can query the resolver
OPNsense Unbound has an access-list model under Services → Unbound DNS → Access Lists. The list is matched most-specific-first; the action of the most specific match wins. The actions:
| Action | Effect |
|---|---|
| Allow | Permit recursive queries from the matched subnet |
| Deny | Silently drop queries from the matched subnet |
| Refuse | Return REFUSED rcode, then drop |
| Allow Snoop | Permit recursive and non-recursive queries (cache snooping; restrict to admin hosts) |
| Deny Non-local | Permit only local-data queries |
| Refuse Non-local | Permit only local-data queries, return REFUSED for everything else |
OPNsense auto-creates Allow entries for every interface that Unbound listens on. The operator typically does not need to add entries; the operator needs to verify the entries match the intended audience and remove the auto-entries for interfaces that should not be resolvers.
The discipline: the access-list page is part of the change review. Every Unbound change that touches the listening list should also touch the access-list page, and the access-list page should match the firewall’s intended segment of resolution service.
$ unbound-control -c /var/unbound/unbound.conf local_zones | headforward.local
lan.internal
dmz.internal
guest.wifi
0.0.0.0.10.in-addr.arpa
1.0.0.10.in-addr.arpaIllustrative output
DNSSEC: validation, not encryption
DNSSEC proves that the answer the resolver returned is the answer the authoritative server published, and that the authoritative server is who it claims to be. It does not encrypt DNS; it validates DNS. A DNSSEC-validating resolver returns SERVFAIL for any answer that fails to validate, rather than returning a possibly-poisoned answer.
OPNsense enables DNSSEC by default. The operator should leave it on. The failure modes:
The signed zone with a broken chain. The zone is signed but the parent is not, or a key has rolled and a downstream resolver has not picked up the new DS record. The answer is signed but the chain does not close, so the resolver returns SERVFAIL. The fix is upstream of the firewall — sign the parent zone or wait for the chain to repair.
Harden DNSSEC data (advanced setting). This option makes Unbound require DNSSEC data for trust-anchored zones. If the data is missing, the zone is marked bogus. The setting is conservative; turning it on can break zones that are transitioning in or out of DNSSEC. The default is on for the root anchor.
The trust anchor. Unbound ships with the root trust anchor (the key that signs the root zone). The anchor must be updated when the root key rolls; OPNsense updates the anchor through the firmware. If the anchor is stale, validation fails for any name in the hierarchy.
The operational check: drill -S example.com prints the chain of trust from the root. A green chain with ad (authenticated data) flag set is a healthy DNSSEC-validating resolver.
Prefetch and serve-expired: cache freshness
Two settings decide whether the cache stays warm.
Prefetch support. Unbound refreshes cache entries before their TTL expires, keeping popular entries hot. The setting causes about 10% more outbound DNS traffic on the upstream side (root, TLD, authoritative), and keeps the latency of cold-cache misses low for the next client. For a production firewall serving a LAN, prefetch is the right choice.
Serve expired responses. When an entry has expired and Unbound is trying to refresh it, serve the stale entry to the client while the refresh is in flight, rather than waiting. RFC 8767 calls this serve-stale; OPNsense implements it as a configurable timeout. The trade-off: clients get faster answers on the first query after expiry, at the cost of briefly serving data the upstream might already have changed. For most production use cases the trade-off favours serving stale.
Cache size: matching the workload
The OPNsense defaults for the message cache and the RRset cache are conservative — appropriate for a small LAN. The size to set depends on the workload:
| Workload | Message cache (suggested) | Reasoning |
|---|---|---|
| Home / SOHO LAN (10 clients) | 4 MB (default) | Default is fine |
| Office LAN (100 clients) | 16 MB | Higher hit rate; less cold-cache pain |
| Datacenter segment (1000+ clients) | 64 MB or more | Cache becomes the dominant answer path |
| Resolver serving many short-lived names (CDN-heavy) | 32 MB | More entries to hold |
$ unbound-control -c /var/unbound/unbound.conf stats | grep -E 'cache|cachemiss|cachehit'total.num.cachehits: 4612183
total.num.cachemiss: 211734
msg.cache.count: 4821
msg.cache.max: 4096
rrset.cache.count: 9843
rrset.cache.max: 8192Illustrative output
A cache-hit ratio above 95% means the cache is doing useful work; below 90% the cache is too small or the workload is too diverse to fit. The right knob to turn is the message-cache size (the RRset cache follows).
Hardening options
Five hardening options are worth enabling on a production resolver:
Hide Identity. Returns REFUSED for id.server and hostname.bind queries. Prevents fingerprinting that the resolver is Unbound on FreeBSD.
Hide Version. Returns REFUSED for version.server and version.bind queries. Same rationale.
Harden DNSSEC data. Already discussed. Defaults to on; leave it on.
Harden Refer-Header. Limits the information sent to upstream servers in the Referer-Source field. Privacy hardening.
Unwanted Reply Threshold. Monitors the count of unwanted replies per thread and clears the cache when the threshold is reached. The defensive action is meant to defeat cache poisoning — if an attacker manages to inject unwanted replies, the cache clears and starts fresh.
Verifying the configuration
The first verification step after every Unbound change is to read the generated configuration and confirm it matches the intent.
$ configctl unbound checkreading config file /var/unbound/unbound.conf
checking syntax
zone lan.internal
zone dmz.internal
... ok
no errors
unbound.conf line 47: notice: 'auth-zone' option is deprecated
unbound-checkconf completed without errors, 1 warningIllustrative output
configctl unbound check is the safe read-only-or-near-it check; it parses the generated file with unbound-checkconf and reports errors. Run it before every Apply and again after every Apply if there was any doubt.
Summary
- The default OPNsense Unbound listens on every interface; production should listen on the interfaces that should serve DNS, and never on WAN.
- Access lists auto-create allow entries for listening interfaces; verify them as part of the change review and remove the entries for interfaces that should not be resolvers.
- DNSSEC validation is enabled by default and should stay enabled; SERVFAIL is the correct response to a chain-of-trust failure.
- Prefetch keeps the cache warm at the cost of about 10% more outbound DNS traffic; serve-expired answers brief stale data while a refresh is in flight.
- Cache size should match the workload — small LANs run on defaults, larger estates raise the message-cache size.
- Hardening options (hide identity, hide version, harden DNSSEC data, unwanted reply threshold) reduce the attack surface but should be tested on a non-production resolver before promotion.
- Verify every change with
configctl unbound checkand confirm the generated/var/unbound/unbound.confmatches intent.
Knowledge check · 4 questions
Q1. An OPNsense firewall is installed with default Unbound settings. The WAN interface is up. A host on the public Internet runs drill @<firewall_wan_ip> example.com and gets a real answer. What is the production concern?
Q2. DNSSEC validation proves that the answer returned by the resolver is the answer the authoritative server published.
Q3. Which of the following are valid hardening options for an OPNsense Unbound resolver? Select all that apply.
Q4. A resolver has msg.cache.count approaching msg.cache.max and the cache-hit ratio is 88%. The operator wants to improve cache performance. What is the right change?
Passing score: 75%. Answers are checked in this browser.