Unbound Cheat Sheet
Overview
Unbound is a validating, recursive, and caching DNS resolver developed by NLnet Labs. It is designed for high performance, security, and standards compliance, with built-in DNSSEC validation as a core feature. Unbound performs recursive resolution starting from the root servers, caching results to speed up subsequent queries. It is widely deployed as a local recursive resolver on servers, network appliances, and as the default DNS resolver in many Linux distributions and firewalls (including OPNsense and pfSense).
Unbound supports modern DNS privacy protocols including DNS-over-TLS (DoT) on port 853 and DNS-over-HTTPS (DoH), allowing encrypted DNS queries. It offers extensive access control, response policy zones (RPZ) for DNS-based filtering, local zone definitions for internal DNS, and stub/forward zones for split-horizon DNS. Unbound can also serve as a DNS forwarder, sending queries to upstream resolvers instead of performing full recursion. Its modular architecture includes support for Python scripting, Redis caching, and DNSTAP logging for analysis.
Installation
Package Installation
# Ubuntu/Debian
sudo apt install unbound unbound-anchor
# RHEL/CentOS/Rocky
sudo dnf install unbound
# macOS
brew install unbound
# FreeBSD
pkg install unbound
# Fetch root trust anchor for DNSSEC
sudo unbound-anchor -a /var/lib/unbound/root.key
# Fetch root hints
sudo wget -O /var/lib/unbound/root.hints https://www.internic.net/domain/named.cache
# Enable and start
sudo systemctl enable unbound
sudo systemctl start unbound
Core Commands
| Command | Description |
|---|---|
unbound-control status | Show server status |
unbound-control reload | Reload configuration |
unbound-control stats | Show statistics |
unbound-control stats_noreset | Stats without resetting counters |
unbound-control dump_cache | Dump cache contents |
unbound-control load_cache | Load cache from file |
unbound-control flush <name> | Flush specific name from cache |
unbound-control flush_zone <zone> | Flush entire zone from cache |
unbound-control flush_type <name> <type> | Flush specific record type |
unbound-control list_forwards | Show configured forwarders |
unbound-control list_stubs | Show configured stub zones |
unbound-control list_local_zones | List local zones |
unbound-control local_data <rr> | Add local data entry |
unbound-control local_data_remove <name> | Remove local data |
Cache Management
# View cache statistics
sudo unbound-control stats_noreset | grep cache
# Flush single name
sudo unbound-control flush example.com
# Flush entire zone
sudo unbound-control flush_zone example.com
# Flush all cached data
sudo unbound-control flush_zone .
# Dump cache to file
sudo unbound-control dump_cache > /tmp/cache.dump
# Restore cache from file
sudo unbound-control load_cache < /tmp/cache.dump
Configuration
Basic Recursive Resolver (/etc/unbound/unbound.conf)
server:
# Network
interface: 0.0.0.0
interface: ::0
port: 53
# Access control
access-control: 127.0.0.0/8 allow
access-control: 10.0.0.0/8 allow
access-control: 172.16.0.0/12 allow
access-control: 192.168.0.0/16 allow
access-control: ::1/128 allow
access-control: 0.0.0.0/0 refuse
# Performance
num-threads: 4
msg-cache-slabs: 4
rrset-cache-slabs: 4
infra-cache-slabs: 4
key-cache-slabs: 4
msg-cache-size: 256m
rrset-cache-size: 512m
outgoing-range: 8192
num-queries-per-thread: 4096
# DNSSEC validation
auto-trust-anchor-file: /var/lib/unbound/root.key
val-clean-additional: yes
# Root hints
root-hints: /var/lib/unbound/root.hints
# Privacy
hide-identity: yes
hide-version: yes
harden-glue: yes
harden-dnssec-stripped: yes
harden-referral-path: yes
use-caps-for-id: yes
qname-minimisation: yes
# Logging
verbosity: 1
log-queries: no
log-replies: no
logfile: /var/log/unbound/unbound.log
# Cache settings
cache-min-ttl: 300
cache-max-ttl: 86400
prefetch: yes
prefetch-key: yes
serve-expired: yes
serve-expired-ttl: 86400
# Misc
do-ip4: yes
do-ip6: yes
do-tcp: yes
do-udp: yes
edns-buffer-size: 1232
# Remote control
remote-control:
control-enable: yes
control-interface: 127.0.0.1
control-port: 8953
server-key-file: /etc/unbound/unbound_server.key
server-cert-file: /etc/unbound/unbound_server.pem
control-key-file: /etc/unbound/unbound_control.key
control-cert-file: /etc/unbound/unbound_control.pem
Setup Remote Control
# Generate control keys
sudo unbound-control-setup
# Test
sudo unbound-control status
DNS-over-TLS (DoT) / DNS-over-HTTPS (DoH)
Forwarding to DoT Upstream
server:
# Enable TLS for outgoing queries
tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
forward-zone:
name: "."
forward-tls-upstream: yes
# Cloudflare
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 1.0.0.1@853#cloudflare-dns.com
# Google
forward-addr: 8.8.8.8@853#dns.google
forward-addr: 8.8.4.4@853#dns.google
Serving DoT (Incoming)
server:
interface: 0.0.0.0@853
tls-service-key: /etc/unbound/server.key
tls-service-pem: /etc/unbound/server.pem
tls-port: 853
Serving DoH (Incoming)
server:
interface: 0.0.0.0@443
tls-service-key: /etc/unbound/server.key
tls-service-pem: /etc/unbound/server.pem
https-port: 443
Local Zones and DNS Filtering
Local DNS Records
server:
# Internal domain
local-zone: "home.local." static
local-data: "router.home.local. IN A 192.168.1.1"
local-data: "nas.home.local. IN A 192.168.1.10"
local-data: "server.home.local. IN A 192.168.1.20"
local-data-ptr: "192.168.1.1 router.home.local."
local-data-ptr: "192.168.1.10 nas.home.local."
local-data-ptr: "192.168.1.20 server.home.local."
# Block ad domains
local-zone: "ads.example.com." always_refuse
local-zone: "tracking.example.com." always_nxdomain
# Redirect domain
local-zone: "override.example.com." redirect
local-data: "override.example.com. A 10.0.0.1"
Response Policy Zone (RPZ)
rpz:
name: "rpz.block"
zonefile: /etc/unbound/rpz.block.zone
rpz-action-override: nxdomain
rpz-log: yes
rpz-log-name: "rpz-block"
; /etc/unbound/rpz.block.zone
$TTL 300
@ SOA localhost. root.localhost. 1 3600 900 86400 300
@ NS localhost.
; Block domains
malware.example.com CNAME .
badsite.example.com CNAME .
; Block all subdomains
*.malvertising.example.com CNAME .
Split DNS / Conditional Forwarding
# Forward internal domain to corporate DNS
forward-zone:
name: "corp.example.com"
forward-addr: 10.0.0.53
forward-addr: 10.0.0.54
# Forward reverse DNS for internal ranges
forward-zone:
name: "10.in-addr.arpa."
forward-addr: 10.0.0.53
# Stub zone for a specific domain
stub-zone:
name: "internal.example.com"
stub-addr: 10.0.0.53
# Forward everything else
forward-zone:
name: "."
forward-addr: 1.1.1.1
forward-addr: 8.8.8.8
Advanced Usage
DNSTAP Logging
dnstap:
dnstap-enable: yes
dnstap-socket-path: /var/run/unbound/dnstap.sock
dnstap-send-identity: yes
dnstap-send-version: yes
dnstap-log-resolver-query-messages: yes
dnstap-log-resolver-response-messages: yes
dnstap-log-client-query-messages: yes
dnstap-log-client-response-messages: yes
Python Module
server:
module-config: "python validator iterator"
python:
python-script: /etc/unbound/custom_module.py
Redis Cache Backend
server:
module-config: "cachedb validator iterator"
cachedb:
backend: "redis"
redis-server-host: 127.0.0.1
redis-server-port: 6379
redis-timeout: 100
redis-expire-records: yes
Monitoring
# Show all statistics
sudo unbound-control stats_noreset
# Key metrics
sudo unbound-control stats_noreset | grep -E "total\.(num|requestlist)"
# Check DNSSEC validation
dig @127.0.0.1 dnssec-failed.org # Should return SERVFAIL
dig @127.0.0.1 +dnssec example.com # Should show AD flag
# Test resolution
dig @127.0.0.1 example.com A
dig @127.0.0.1 example.com AAAA +short
Troubleshooting
| Issue | Solution |
|---|---|
unbound-control connection refused | Run unbound-control-setup to generate keys; enable remote-control |
| DNSSEC validation failures | Update root anchor: unbound-anchor -a /var/lib/unbound/root.key |
| Slow first queries | Enable prefetch: yes and serve-expired: yes; increase cache sizes |
could not bind to port 53 | Stop systemd-resolved: sudo systemctl disable --now systemd-resolved |
| Access denied for clients | Check access-control entries match client subnet |
| Forward zone not resolving | Verify upstream DNS is reachable; check TLS settings if using DoT |
| High memory usage | Reduce msg-cache-size and rrset-cache-size |