Securing Unbound DNS: Caching, Authoritative Zones, and Hardening Strategies
Unbound is a validating, recursive, and caching DNS resolver designed with modularity, high performance, and security at its core. Unlike monolithic BIND setups, Unbound separates recursive resolution from authoritative hosting, making it an ideal front-line resolver for self-hosted home labs and enterprise environments alike.
By hosting your own DNS resolver, you gain granular control over your network traffic, eliminate ISP DNS logging, enforce DNSSEC validation, and significantly speed up lookups through local caching.
1. Installation and Initial Setup
On Debian or Ubuntu-based distributions, install Unbound via the official repositories:
sudo apt update
sudo apt install unbound unbound-host dnsutils
The systemd-resolved Conflict
Modern Linux distributions often use systemd-resolved, which binds to port 53 locally. If you try to start Unbound immediately, it will likely fail. You must disable the systemd-resolved stub listener first:
- Edit
/etc/systemd/resolved.confand setDNSStubListener=no. - Restart systemd-resolved:
sudo systemctl restart systemd-resolved. - Update your
resolv.confto point to localhost:sudo rm /etc/resolv.conf sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf - Start Unbound:
sudo systemctl enable --now unbound.
2. Core Architectural Modes & Configuration Examples
Rather than throwing everything into one file, it is best practice to place the main configuration in /etc/unbound/unbound.conf and modular configurations in /etc/unbound/unbound.conf.d/.
Below is the Base Hardened Configuration (/etc/unbound/unbound.conf), followed by specific use-case examples you can append or include.
A. The Hardened Base Configuration
This configuration locks down access, randomizes queries, and sets up high-performance caching.
server:
# Verbosity level (0 = only errors, 1 = operational info)
verbosity: 1
# Interface binding (Listen on localhost and specific LAN interfaces)
interface: 127.0.0.1
interface: 10.0.0.1 # Replace with your internal server IP
# IPv4/IPv6 preferences
do-ip4: yes
do-udp: yes
do-tcp: yes
do-ip6: no # Set to 'yes' if your network is dual-stack
prefer-ip4: yes
# Access control: Permit trusted local subnets, refuse public recursion
access-control: 127.0.0.0/8 allow
access-control: 10.0.0.0/8 allow # Adjust to match your VLAN/LAN segments
access-control: 0.0.0.0/0 refuse
# Privacy & Fingerprint hardening
hide-identity: yes
hide-version: yes
harden-glue: yes
harden-dnssec-stripped: yes
# 0x20 bit randomization to foil spoofing attacks
use-caps-for-id: yes
# Performance & Caching tuning
num-threads: 2
msg-cache-size: 64m
rrset-cache-size: 128m
infra-cache-numhosts: 10000
cache-min-ttl: 3600 # Force caching for at least 1 hour to speed up lookups
# DNSSEC Trust Anchor
auto-trust-anchor-file: "/var/lib/unbound/root.key"
B. Example 1: DNS-over-TLS (DoT) Forwarder
Instead of querying root servers directly (which happens in plain text), you can configure Unbound to encrypt all outbound DNS queries and forward them to a trusted upstream provider like Quad9 or Cloudflare.
Add this block to the bottom of your configuration:
forward-zone:
name: "."
forward-tls-upstream: yes
# Quad9 (Primary and Secondary)
forward-addr: 9.9.9.9@853#dns.quad9.net
forward-addr: 149.112.112.112@853#dns.quad9.net
# Cloudflare (Fallback)
forward-addr: 1.1.1.1@853#cloudflare-dns.com
C. Example 2: Local DNS Records (Home Lab / Split-Horizon)
Unbound can act as an authoritative server for your internal network, allowing you to use custom domain names (like .home.arpa) without exposing them to the internet.
server:
# Tell Unbound this zone is strictly local
private-domain: "home.arpa"
# Define the local zone
local-zone: "home.arpa." static
# Map internal hostnames to IP addresses
local-data: "router.home.arpa. IN A 10.0.0.1"
local-data: "proxmox.home.arpa. IN A 10.0.0.20"
local-data: "nas.home.arpa. IN A 10.0.0.30"
# Reverse DNS lookups (PTR records) for your local IPs
local-data-ptr: "10.0.0.1 router.home.arpa"
local-data-ptr: "10.0.0.20 proxmox.home.arpa"
D. Example 3: DNS Sinkhole (Ad and Malware Blocking)
You can use Unbound to block known malicious domains or telemetry servers by routing them to a dead end (like 0.0.0.0), similar to how Pi-hole operates.
server:
# Block specific domains
local-zone: "telemetry.microsoft.com" always_nxdomain
local-zone: "doubleclick.net" always_nxdomain
# Or route them to a blackhole IP
local-zone: "ads.example.com" redirect
local-data: "ads.example.com A 0.0.0.0"
3. Key Security Best Practices
- Enforce Strict Access Control: Never configure Unbound to listen on
0.0.0.0or a public IP without explicitaccess-controlrestrictions. An open recursive resolver can easily be weaponized by attackers in DNS amplification DDoS attacks. - Enable DNSSEC Validation: Keep
auto-trust-anchor-fileactive. This ensures cryptographic verification of DNS responses, preventing cache poisoning and man-in-the-middle record tampering. If a domain’s DNSSEC signature is invalid, Unbound will protect your network by dropping the query. - Minimize Information Disclosure: Setting
hide-identity: yesandhide-version: yesprevents attackers from queryingversion.serverorid.servervia TXT records to fingerprint your software stack and look for known vulnerabilities. - Rate Limiting: Protect your resolver from being overwhelmed by local rogue devices by implementing rate limiting:
server: ratelimit: 1000 # Max queries per second per zone ip-ratelimit: 200 # Max queries per second per IP
4. Troubleshooting and Diagnostics
DNS issues can cause network-wide outages. Here is how to diagnose common Unbound problems.
Always Check Syntax First
Before restarting the Unbound service, always check your configuration file for syntax errors:
unbound-checkconf /etc/unbound/unbound.conf
If it returns no output, your configuration is valid.
Everything returns SERVFAIL
The Problem: Clients can connect to Unbound, but every query returns a SERVFAIL status.
The Fix:
- Time Synchronization: DNSSEC validation relies heavily on accurate time. If your server’s clock is significantly skewed, all DNSSEC checks will fail. Ensure NTP is running:
timedatectl status. - Missing Trust Anchor: The
root.keyfile might be missing or corrupt. Runsudo unbound-anchor -a /var/lib/unbound/root.keyto fetch a fresh key. - Upstream Connectivity: If you are forwarding queries, ensure port 53 (or 853 for DoT) is not blocked by your ISP or firewall.
Unbound fails to start: Address already in use
The Problem: Another service is listening on port 53.
The Fix:
Run sudo ss -tulpn | grep :53 to find the culprit. It is almost always systemd-resolved (see Section 1 for the fix) or dnsmasq running on the same machine.
Testing Queries Locally
Use dig to verify Unbound is resolving correctly and caching responses.
- Query your local Unbound server:
dig @127.0.0.1 example.com - Check the Query Time: The first query might take 30-100ms. Run the same command a second time. If caching is working, the query time should drop to
0 msec. - Verify DNSSEC:
Look for the
dig @127.0.0.1 sigok.verteiltesysteme.net +dnssecad(Authentic Data) flag in the header of the output. If it is present, DNSSEC validation is working correctly.
Permission Errors (AppArmor/SELinux)
If you moved your log files or trust anchor files to a non-standard directory and Unbound fails to start with “Permission denied”, check your mandatory access control logs.
- Ubuntu/Debian (AppArmor): Check
/var/log/syslog. You may need to edit/etc/apparmor.d/usr.sbin.unboundto whitelist your custom directories. - RHEL/Fedora (SELinux): Check
/var/log/audit/audit.log.