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.conf and set DNSStubListener=no.
- Restart systemd-resolved:
sudo systemctl restart systemd-resolved.
- Update your
resolv.conf to 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
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.key file might be missing or corrupt. Run sudo unbound-anchor -a /var/lib/unbound/root.key to 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:
dig @127.0.0.1 sigok.verteiltesysteme.net +dnssec
Look for the ad (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.unbound to whitelist your custom directories.
- RHEL/Fedora (SELinux): Check
/var/log/audit/audit.log.