Unbound Installation and Configuration Guide
STEP 1: update and upgrade system
sudo apt update && sudo apt upgrade -yy
Step 1.5: install vm tools for vmware esxi control (Optional)
sudo apt install open-vm-tools
Step 2: Disable systemd-resolved’s DNSStubListener
sudo nano /etc/systemd/resolved.conf
#Find the line #DNSStubListener=yes and change it to:
DNSStubListener=no
#Save the file (Ctrl+O, Enter, Ctrl+X)
#Then restart systemd-resolved:
sudo systemctl restart systemd-resolved
STEP 3: Install Unbound & outher apps
sudo apt update && sudo apt install -y unbound dnsutils tcpdump bind9-host ufw ca-certificates
#Package notes:
#* unbound – The validating, recursive, and caching DNS resolver
#* dnsutils – Includes diagnostic tools like dig and nslookup
#* tcpdump – Command-line packet analyzer for troubleshooting network/DNS traffic
#* bind9-host – DNS lookup utility (provides the host command)
#* ufw – Uncomplicated Firewall to secure the server ports
#* ca-certificates – Common CA certificates for secure TLS/HTTPS connections
STEP 4: Get DNSSEC Root Key
sudo unbound-anchor -a /var/lib/unbound/root.key
#if it fails run
ls -l /var/lib/unbound/root.key
#if it shows your key then its fine
STEP 5: Download latest root hints file from IANA:
sudo curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.root
STEP 6: Set Permissions on Root Trust Files
sudo chmod 644 /var/lib/unbound/root.hints
sudo chmod 644 /var/lib/unbound/root.key
sudo chown unbound:unbound /var/lib/unbound/root.hints
sudo chown unbound:unbound /var/lib/unbound/root.key
Step 7: automate updating root hints file every month:
sudo crontab -e
@monthly curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.root
STEP 8: Backup and Replace Default Config
sudo mv /etc/unbound/unbound.conf /etc/unbound/unbound.conf.bak
sudo nano /etc/unbound/unbound.conf
#Paste the following configuration (adapted for IP 10.10.10.4):
server:
verbosity: 1 # Log level (0 = quiet, 1 = operational info, up to 5 for debug)
# 0 Quiet Logs only critical errors
# 1 Operational info (Default for production; logs key events/warnings)
# 2 More detailed (Adds basic query logging and more diagnostics)
# 3 Query-level info (Shows every query received and how it was handled)
# 4+ Debug mode Very verbose (not suitable for production; mostly for dev)
# check logs with “journalctl -u unbound” OR “tail -f /var/log/syslog”
interface: 10.10.10.4 # Listen on LAN IP (use this for AdGuard to point to)
interface: 127.0.0.1 # Also listen on localhost for local queries
port: 53 # Standard DNS port
do-ip4: yes # Enable IPv4 DNS queries
do-ip6: no # Disables IPV6 DNS queries
do-udp: yes # Enable DNS over UDP (default for most queries)
do-tcp: yes # Enable DNS over TCP (for larger queries and fallback)
# Access control: allow only local devices to query Unbound
access-control: 127.0.0.0/8 allow # Allow localhost
access-control: 192.168.1.0/24 allow # Allow your LAN subnet
access-control: 10.10.0.0/16 allow # Allow your LAN subnet
access-control: 0.0.0.0/0 refuse # Deny all others by default
# Privacy and security hardening
hide-identity: yes # Hide server identity in DNS responses
hide-version: yes # Hide Unbound version in DNS responses
harden-glue: yes # Reject unverified glue records (to prevent poisoning)
harden-dnssec-stripped: yes # Reject responses that appear to have DNSSEC removed
harden-referral-path: yes # Verify DNS referral chains for integrity
use-caps-for-id: yes # Use 0x20-bit encoding to harden against DNS spoofing
qname-minimisation: yes # Send minimal query info to upstream servers (privacy)
aggressive-nsec: yes # DNSSEC hardening
do-not-query-localhost: yes # Avoid querying local resolvers
val-permissive-mode: no # Enforce strict DNSSEC validation
# DNS rebinding protection (block responses that point back to a local ip address)
private-address: 192.168.0.0/16 # Block local subnet
#private-address: 10.0.0.0/8 # Block local subnet
private-address: 172.16.0.0/12 # Block local subnet
private-address: 127.0.0.0/8 # Block loopback
private-address: ::1 # Block IPv6 loopback
private-address: fc00::/7 # Block IPv6 ULA
private-address: fe80::/10 # Block IPv6 link-local
# Allow private IPs in homelab responses (allows the .homelab extention to be resolved to private ip address)
domain-insecure: “homelab”
# Rate limiting (anti-DNS abuse)
ratelimit: 100 # Max queries per second from clients
ratelimit-slabs: 4 # Memory slab count (power of 2)
ip-ratelimit: 50 # Per-IP rate limit (queries/sec)
# Caching and performance
prefetch: yes # Preemptively refresh cache when TTL is nearing expiry
prefetch-key: yes # Prefetch DNSSEC keys along with records
msg-cache-size: 256m # Message cache (stores responses)
rrset-cache-size: 512m # RRset cache (stores DNS record sets)
# Unbound recommends keeping rrset-cache-size about 2× larger than msg-cache-size.
# server with 2 GB of RAM msg-cache-size: 64m/rrset-cache-size: 128m
# server with 4–8 GB of RAM msg-cache-size: 256m/rrset-cache-size: 512m
cache-min-ttl: 86400 # Minimum time (in seconds) to keep a cached entry (1 day)
cache-max-ttl: 604800 # Maximum time (in seconds) to keep a cached entry (7 day)
# Stale DNS serving in case upstream becomes unavailable
serve-expired: yes # Allow serving expired (stale) DNS entries
serve-expired-ttl: 604800 # Serve stale entries for up to 7 day
serve-expired-client-timeout: 1800 # Keep trying to refresh stale data for 30 minutes in background
# Performance tuning
num-threads: 2 # Number of threads (adjust based on CPU cores)
so-reuseport: yes # Enable socket reuse port for better multi-thread packet distribution
so-rcvbuf: 4m # Socket receive buffer size
so-sndbuf: 4m # Socket send buffer size
edns-buffer-size: 1232 # EDNS buffer size for DNS messages (mitigates fragmentation)
rrset-roundrobin: yes # Randomize record order in responses (load balancing benefit)
# Disable unnecessary services
statistics-interval: 0 # Disable periodic stats logging
extended-statistics: no # Disable detailed statistics collection
# TLS certificate bundle (for validating DoT upstream)
# Path to system CA bundle
tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
# DNSSEC trust anchor configuration
# Root trust anchor for validating DNSSEC signatures
auto-trust-anchor-file: “/var/lib/unbound/root.key”
# root hints tell unbound where to find ip address for root server
root-hints: “/var/lib/unbound/root.hints”
remote-control: # Disable remote control interface
control-enable: no
# Forward .homelab queries to BIND
forward-zone:
name: “homelab” # Matches all *.homelab queries
forward-addr: 10.10.10.6 # IP address of your BIND server
# The below is the forwarding section, if you wish unbound to send dns queries straight to root server; comment all of the below out
# Forwarding section: send all queries to Quad9 via DNS-over-TLS
forward-zone:
name: “.” # Forward all queries (root zone)
forward-tls-upstream: yes # Use DNS-over-TLS to encrypt queries to forward-addr
forward-addr: 9.9.9.9@853 # Quad9 DoT primary server
forward-addr: 149.112.112.112@853 # Quad9 DoT secondary server
forward-addr: 2620:fe::fe@853 # Quad9 IPv6 DoT primary (optional)
forward-addr: 2620:fe::9@853 # Quad9 IPv6 DoT secondary (optional)
# Forwarding section: send all queries to Quad9 via DNS-over-HTTPS
#forward-zone:
# name: “.”
# forward-ssl-upstream: yes # Use TLS for DoH
# forward-host: “dns.quad9.net” # Quad9 DoH server hostname
# forward-addr: 9.9.9.9 # Quad9 IPv4 address (used for connecting)
# forward-addr: 149.112.112.112 # Quad9 secondary IPv4
# forward-addr: 2620:fe::fe # Quad9 IPv6 primary
# forward-addr: 2620:fe::9 # Quad9 IPv6 secondary
# Alternatively, with Unbound version 1.17.0 and later, you can specify DoH URLs directly:
# unbound -V (to verify unbound version)
#forward-zone:
# name: “.”
# forward-doh-url: “https://dns.quad9.net/dns-query” # Primary DoH URL
# forward-doh-url: “https://dns9.quad9.net/dns-query” # secondary DoH URL
# Forwarding section: send all queries to DNS.SB via DNS-over-TLS
#forward-zone:
# name: “.”
# forward-tls-upstream: yes
# forward-addr: 185.222.222.222@853#dot.sb # DNS.SB primary IPv4 DoT
# forward-addr: 45.11.45.11@853#dot.sb # DNS.SB secondary IPv4 DoT
# forward-addr: 2a09::@853#dot.sb # DNS.SB IPv6 DoT
# forward-addr: 2a11::@853#dot.sb # DNS.SB IPv6 DoT
# Forwarding section: send all queries to Mullvad via DNS-over-TLS
#forward-zone:
# name: “.”
# forward-tls-upstream: yes
# forward-addr: 194.242.2.4@853#base.dns.mullvad.net # Mullvad Base IPv4 DoT (Blocks Ads & Trackers)
# forward-addr: 2a07:e340::4@853#base.dns.mullvad.net # Mullvad Base IPv6 DoT (Blocks Ads & Trackers)
# Forwarding section: send all queries to UncensoredDNS via DNS-over-TLS
#forward-zone:
# name: “.”
# forward-tls-upstream: yes
# forward-addr: 91.239.100.100@853 # UncensoredDNS primary IPv4 DoT
# forward-addr: 89.233.43.71@853 # UncensoredDNS secondary IPv4 DoT
# forward-addr: 2a01:3a0:53:53::1@853 # UncensoredDNS IPv6 DoT
# forward-addr: 2a01:3a0:53:53::2@853 # UncensoredDNS IPv6 DoT
#Save with Ctrl+O, then Enter, then Ctrl+X to exit.
STEP 9: Run a quick config test:
sudo unbound-checkconf
STEP 10: Enable and Start Unbound
sudo systemctl enable unbound
sudo systemctl restart unbound
Step 11: UFW Rules
# Set default policies (deny all incoming, allow all outgoing)
# IMPORTANT: Do this FIRST to ensure security by default
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (so you don’t lock yourself out)
# This rule is crucial and should be added BEFORE enabling UFW
sudo ufw allow ssh
# Allow DNS (Unbound) from your local network (LAN)
# This allows devices on your 192.168.1.x network to query Unbound
sudo ufw allow from 192.168.1.0/24 to any port 53 proto udp
sudo ufw allow from 192.168.1.0/24 to any port 53 proto tcp
sudo ufw allow from 10.10.0.0/16 to any port 53 proto udp
sudo ufw allow from 10.10.0.0/16 to any port 53 proto tcp
# Allow localhost access for Unbound (for the server itself and AdGuard Home if on the same machine)
sudo ufw allow from 127.0.0.1 to any port 53 proto udp
sudo ufw allow from 127.0.0.1 to any port 53 proto tcp
# Enable UFW (if not already enabled)
# This should be the last step after all allow rules are in place
sudo ufw enable
# Reload UFW (only needed if UFW was already enabled and you modified rules)
# If you just enabled it with the above command, this isn’t strictly necessary immediately after.
# But it’s good to know for future rule changes.
sudo ufw reload
#To check:
sudo systemctl status unbound
STEP 12: Test Unbound
#Try resolving a domain using Unbound:
dig @10.10.10.4 example.com
#You should get a valid response.
dig @10.10.10.4 example.com +stats
#test caching and performance
dig @10.10.10.4 test1.homelab
#test bind .homelab resolution
#To test DNSSEC validation:
dig +dnssec @10.10.10.4 dnssec-failed.org
#If DNSSEC is working, you should get no answer (because that domain is intentionally broken).
