1. What Unbound Is¶
Unbound is a validating, recursive, and caching DNS resolver developed by NLnet Labs (originally with VeriSign and Nominet involvement), designed from the ground up around two priorities BIND's older codebase wasn't originally built for: DNSSEC validation as a first-class feature and a small, security-focused codebase. Where BIND is a general-purpose DNS server that can be authoritative, recursive, or both, Unbound is deliberately scoped to the resolver half of that job — it does not serve as an authoritative server for zones you own (though it can answer authoritatively for small local overrides; see §7). This narrower scope is a design choice, not a limitation: it means less attack surface and a simpler mental model for what the daemon is doing.
Typical roles Unbound fills: - A local caching resolver on a single machine or router, cutting lookup latency and reducing upstream query volume. - A LAN-wide recursive resolver, similar to the RHEL caching-BIND role covered in the companion BIND notes. - A DNS-over-TLS forwarding server — accepting plain DNS from LAN clients and re-encrypting outbound queries over TLS to an upstream provider, which is the specific role your notes below are building. - A DNSSEC-validating layer in front of an otherwise non-validating environment.
2. Installation¶
Debian / Ubuntu¶
sudo apt-get update
sudo apt-get install -y unbound
RHEL / CentOS / Rocky / Alma¶
sudo dnf install -y unbound
Alpine (common in minimal containers)¶
apk add unbound
On Debian/Ubuntu, the package ships a working default configuration and — importantly — a separate include file that seeds Unbound's DNSSEC trust anchor automatically (/etc/unbound/unbound.conf.d/root-auto-trust-anchor-file.conf, pulling in root-auto-trust-anchor-file: "/var/lib/unbound/root.key", refreshed via unbound-anchor). This is easy to miss when hand-rolling a config from scratch, and without it DNSSEC validation has no trust anchor to validate against.
3. Configuration File Structure¶
Unbound's main config is /etc/unbound/unbound.conf, which on most distro packaging simply includes everything under /etc/unbound/unbound.conf.d/*.conf:
include: "/etc/unbound/unbound.conf.d/*.conf"
This lets you split concerns into separate files (main server options, forwarding, TLS, local overrides) the same way BIND's named.conf is split via include statements — each file just needs a server: block (or whichever top-level clause it's extending) rather than one being defined once globally.
Top-level clauses:
| Clause | Purpose |
|---|---|
server: |
Global daemon behavior — listening addresses, access control, logging, DNSSEC, chroot, performance tuning |
forward-zone: |
Send queries for a zone (. for everything) to specific upstream resolvers instead of doing iterative resolution from the root |
stub-zone: |
Query specific authoritative servers directly for a zone, bypassing normal delegation lookup — useful for internal/private zones |
auth-zone: |
Serve a zone authoritatively from a local zone file, alongside normal resolving |
local-zone: / local-data: |
Define local overrides/answers without needing a full zone file (see §7) |
remote-control: |
Configuration for unbound-control, Unbound's equivalent of rndc |
view: |
Split-horizon style per-client answer sets, similar in spirit to BIND views |
4. Core server: Options¶
server:
interface: 192.168.0.52
outgoing-interface: 192.168.0.52
access-control: 192.168.0.0/24 allow
do-daemonize: no
username: "unbound"
chroot: "/var/lib/unbound"
directory: "/etc/unbound"
statistics-interval: 300
statistics-cumulative: yes
extended-statistics: yes
use-syslog: yes
Key points:
interface— addresses to listen on. Unlike BIND, Unbound doesn't implicitly bind everything; each address (and, if non-default, port) needs an explicitinterface:line. Multiple lines stack.outgoing-interface— the source address Unbound uses when sending queries upstream. For a multi-homed box, pin this the same way you'd pin BIND'slisten-onto avoid queries leaving on an unintended interface.access-control— Unbound's equivalent of BIND'sallow-recursion/allow-query. Unlike BIND's ACL blocks, this isn't a separately named list — each line is<netblock> <action>(allow,deny,refuse,allow_snoop, etc.), evaluated with longest-prefix-match rather than first-match order. There is no implicit default-deny for anything not listed only if nothing at all is specified — an explicitaccess-control: 0.0.0.0/0 refuse(or simply never binding a public interface) is what actually keeps this resolver from becoming an open resolver; don't rely on omission alone.chroot— confines the daemon to the given directory, the same protective idea as BIND's chroot jail, but built into Unbound's core config rather than a separate packaging variant. All other paths in the config (log files, key files, control socket) are resolved inside this jail once set — the single most common Unbound misconfiguration is a path that works fine withchroot: ""and silently breaks once chroot is enabled, because the referenced file doesn't exist at that path inside the jail.directory— working directory for relative paths in the rest of the config, evaluated after chrooting.username— user to drop privileges to after binding the (privileged, <1024) listening port.do-daemonize— should benowhen run under systemd, which handles backgrounding itself; set toyesonly for old-style init/manual invocation.- Statistics —
statistics-intervalperiodically logs summary counters;extended-statisticsbreaks them down by query type/response code, at a small CPU cost, and is only visible viaunbound-control statsor in the log, not exposed any other way by default.
5. Recursive vs. Forwarding Mode¶
By default, with no forward-zone, Unbound resolves recursively from the root exactly as described in the general DNS guide — walking root → TLD → authoritative servers itself, caching along the way. This is the architecturally "purest" mode and what Unbound was originally optimized for.
Adding a forward zone changes this for the covered namespace:
forward-zone:
name: "."
forward-addr: 208.67.222.222 # OpenDNS
forward-addr: 208.67.220.220
forward-addr: 8.8.8.8 # Google
forward-addr: 8.8.4.4
name: "." means "forward everything" — Unbound stops doing its own iterative resolution entirely and instead hands every query to one of the listed upstreams, still validating DNSSEC on the answers it gets back (validation and forwarding are independent — forwarding changes who answers, not whether the response is checked). Multiple forward-addr lines are tried for redundancy/load distribution, not concatenated into one combined answer.
This is a real trade-off, not just configuration mechanics: - Recursive (no forwarders): no dependency on any third party's resolver, but every uncached query pays the full root→TLD→authoritative latency, and Unbound's own IP is what shows up in query logs at every authoritative server it touches. - Forwarding: faster in practice (upstream providers run large, warm caches), but you're now trusting that provider with your query stream and depending on their uptime — worth pairing with DoT (§6) at minimum so that trust doesn't also extend to your network path.
A forward-zone can also be scoped narrowly instead of globally, e.g. forwarding only an internal corporate domain to a specific internal resolver while everything else resolves normally — set name: to that specific zone instead of ..
6. DNS over TLS (DoT) Forwarding¶
Since forwarding sends full plaintext queries to a third party over the open network by default, encrypting that leg is the natural next step — this is what turns a plain forwarding resolver into a "DoT forwarding server."
server:
tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
forward-zone:
name: "."
forward-tls-upstream: yes
forward-addr: 9.9.9.9@853#dns.quad9.net
forward-addr: 149.112.112.112@853#dns.quad9.net
tls-cert-bundle— the CA bundle Unbound uses to validate the upstream's TLS certificate. Without this,forward-tls-upstream: yeswill fail to establish trusted connections (behavior differs by build — some fail closed, some warn and fall through; don't rely on silent success).forward-tls-upstream: yes— scoped to theforward-zoneblock, not a globalserver:setting; each forward zone independently chooses whether its upstream connections use TLS.@853— the DoT port (853, distinct from plain DNS's port 53).#dns.quad9.net— the hostname Unbound authenticates the presented certificate against (this is what makes it authenticated TLS rather than merely encrypted-but-unverified; omitting the#hostnamesuffix still encrypts the connection but skips hostname verification, which meaningfully weakens the guarantee).
Common public DoT-capable resolvers, for reference:
| Provider | Address | TLS auth name |
|---|---|---|
| Quad9 | 9.9.9.9, 149.112.112.112 |
dns.quad9.net |
| Cloudflare | 1.1.1.1, 1.0.0.1 |
cloudflare-dns.com |
8.8.8.8, 8.8.4.4 |
dns.google |
Verify DoT is actually being used (rather than silently falling back) with unbound-control stats | grep tls, or a packet capture on port 853 versus 53 outbound.
7. Local Zones and Overrides¶
Where BIND requires a full zone file even for a single overridden name, Unbound supports lightweight inline overrides via local-zone and local-data, without standing up an authoritative zone:
server:
local-zone: "home.example.arpa." static
local-data: "nas.home.example.arpa. IN A 192.168.0.10"
local-data: "printer.home.example.arpa. IN A 192.168.0.20"
local-data-ptr: "192.168.0.10 nas.home.example.arpa."
local-zone types control fallback behavior for names not explicitly covered by a local-data line within that zone:
| Type | Behavior for unmatched names in the zone |
|---|---|
static |
NXDOMAIN / NODATA — nothing outside the explicit local-data entries exists |
transparent |
Falls through to normal resolution for anything not explicitly overridden |
redirect |
Every query in the zone gets the same single answer, regardless of the queried name |
deny |
Silently drops queries into the zone |
refuse |
Answers with REFUSED |
nodefault |
Removes one of Unbound's compiled-in default reserved-space zones (e.g., to allow querying an RFC 1918 range normally blocked by default) |
For a full authoritative-style local zone with more than a couple of records, auth-zone: pointing at a real zone file (in standard BBIND-style zone-file syntax) is cleaner than a long list of local-data lines:
auth-zone:
name: "home.example.arpa."
zonefile: "/etc/unbound/home.example.arpa.zone"
8. unbound-control¶
Unbound's equivalent of rndc, used to inspect and manage a running daemon without restarting it. Requires a control key/cert setup, generated once:
sudo unbound-control-setup
with a corresponding config block:
remote-control:
control-enable: yes
control-interface: 127.0.0.1
control-port: 8953
Common commands:
unbound-control status # daemon uptime, version
unbound-control stats # cumulative counters (needs statistics-cumulative)
unbound-control stats_noreset # same, without clearing counters after
unbound-control reload # reload config + flush cache
unbound-control flush example.com # flush one name from cache
unbound-control flush_zone example.com # flush an entire zone's worth of cached entries
unbound-control dump_cache # dump the full cache contents
unbound-control list_forwards # show active forward-zone configuration
9. Logging and Diagnostics¶
server:
use-syslog: yes
verbosity: 1
log-queries: no
verbosityranges 0–5; 1 is a reasonable steady-state level (operational messages, errors), while 3+ approaches per-query tracing and is meant for active troubleshooting only, similar in spirit to leaving BIND'squeriescategory permanently atdebug— both log every lookup and should be dialed back down once a problem is resolved.log-queries: yesindependently logs every incoming query regardless ofverbosity; same privacy/disk caveat applies as BIND's query logging.- With
use-syslog: yes, output goes to the system logger (journalctl -u unboundunder systemd) rather than a dedicated file unless you also configurelogfile:.
10. DNSSEC Validation¶
Validation is on by default in modern Unbound packages via the auto-trust-anchor include mentioned in §2, but the underlying mechanics are worth understanding:
server:
auto-trust-anchor-file: "/var/lib/unbound/root.key"
unbound-anchor (run automatically by the packaged systemd unit before unbound starts, on distros that wire it up) fetches and verifies the current root trust anchor if the file is missing or looks stale, following RFC 5011 automated key rollover — meaning a running Unbound instance can follow a root key rollover without manual intervention, as long as it stays online through the rollover window. A resolver offline for an extended period spanning a key rollover may need unbound-anchor re-run manually on the next start.
Validation failures return SERVFAIL to the client by default rather than the (correct, but unsigned-looking) unvalidated answer — this is the intended fail-closed behavior, but it means a broken clock, a misconfigured trust anchor, or an actual on-path attack all present identically as "everything suddenly returns SERVFAIL," which is the first thing to check when diagnosing.
11. Firewall Considerations¶
Unbound needs inbound UDP+TCP/53 from whichever clients it serves (TCP for large/truncated responses and zone-transfer-adjacent operations even though Unbound isn't itself authoritative), and outbound UDP+TCP/53 (or TCP/853 for DoT upstreams) to whatever it resolves against or forwards to.
# UFW shorthand for local DNS service
sudo ufw allow dns
# More explicit equivalent
sudo ufw allow 53/udp
sudo ufw allow 53/tcp
If forwarding over DoT, also permit outbound 853/tcp to the specific upstream addresses.
12. Unbound vs. BIND, at a Glance¶
| Unbound | BIND | |
|---|---|---|
| Primary role | Recursive/caching resolver | Both authoritative and recursive |
| Authoritative serving | Minimal (local-zone/auth-zone overrides) |
Full-featured, the common choice for hosting real zones |
| DNSSEC validation | On by default, core design goal | Supported, opt-in (dnssec-validation) |
| Config style | server:/forward-zone: blocks, include: globs |
named.conf + included files, zone {} blocks |
| Control utility | unbound-control |
rndc |
| Typical fit | LAN resolver, DoT/DoH forwarding gateway, validating layer | Authoritative DNS hosting, combined authoritative+recursive small deployments |
Running both in the same environment is common: Unbound as the LAN-facing recursive/validating/forwarding layer, BIND as the authoritative server for zones you actually own — each doing the job it's most purpose-built for.
13. Reference Links¶
- Unbound project: https://nlnetlabs.nl/projects/unbound/about/
- Unbound manual pages: https://unbound.docs.nlnetlabs.nl/
- Quad9: https://quad9.net
- Arch Wiki, Unbound: https://wiki.archlinux.org/title/unbound
- Calomel Unbound DNS guide: https://calomel.org/unbound_dns.html