DNS over TLS (DoT) encrypts DNS queries between your resolver and upstream DNS servers, preventing eavesdropping on DNS traffic. Unbound is a popular validating, recursive DNS resolver that supports DoT both as a client (forwarding encrypted queries upstream) and as a server (accepting encrypted queries from clients). This guide covers both configurations.
Installing Unbound
sudo apt install unbound unbound-anchor # Ubuntu/Debian
sudo dnf install unbound # Rocky Linux
# Download root hints
sudo wget -O /etc/unbound/root.hints https://www.internic.net/domain/named.root
# Initialize DNSSEC trust anchor
sudo unbound-anchor -a /etc/unbound/root.key
Unbound as DoT Client (Forward to Encrypted Upstream)
# /etc/unbound/unbound.conf
server:
interface: 127.0.0.1
port: 53
access-control: 127.0.0.0/8 allow
access-control: 10.0.0.0/8 allow
# DNSSEC validation
auto-trust-anchor-file: /etc/unbound/root.key
# Performance
num-threads: 4
msg-cache-size: 128m
rrset-cache-size: 256m
cache-min-ttl: 60
cache-max-ttl: 86400
prefetch: yes
prefetch-key: yes
# Privacy
qname-minimisation: yes
hide-identity: yes
hide-version: yes
# TLS settings for upstream
tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
forward-zone:
name: "."
forward-tls-upstream: yes
# Cloudflare DNS over TLS
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 1.0.0.1@853#cloudflare-dns.com
# Google DNS over TLS
forward-addr: 8.8.8.8@853#dns.google
forward-addr: 8.8.4.4@853#dns.google
# Test configuration and start
sudo unbound-checkconf
sudo systemctl enable --now unbound
# Verify DoT is working
dig @127.0.0.1 example.com +short
# Verify DNSSEC
dig @127.0.0.1 dnssec-failed.org A # Should return SERVFAIL
Unbound as DoT Server (Accept Encrypted Queries)
server:
# Regular DNS on localhost
interface: 127.0.0.1@53
# DoT listener on all interfaces
interface: 0.0.0.0@853
tls-port: 853
tls-service-key: /etc/letsencrypt/live/dns.example.com/privkey.pem
tls-service-pem: /etc/letsencrypt/live/dns.example.com/fullchain.pem
access-control: 0.0.0.0/0 allow
auto-trust-anchor-file: /etc/unbound/root.key
Client Testing
# Test DoT with kdig
kdig @dns.example.com +tls example.com A
# Test with knot-resolver
kresd -n --tls dns.example.com
# Android: Settings → Network → Private DNS → dns.example.com
Local Network Caching Resolver
# Serve your local network with encrypted upstream queries
server:
interface: 0.0.0.0
port: 53
access-control: 192.168.0.0/16 allow
access-control: 10.0.0.0/8 allow
access-control: 127.0.0.0/8 allow
access-control: 0.0.0.0/0 refuse
# Aggressive caching
prefetch: yes
serve-expired: yes
serve-expired-ttl: 86400
forward-zone:
name: "."
forward-tls-upstream: yes
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 9.9.9.9@853#dns.quad9.net
Best Practices
- Always enable DNSSEC validation alongside DoT for complete DNS security
- Use
qname-minimisationto minimize data sent to upstream resolvers - Enable
prefetchto keep popular records in cache before they expire - Configure multiple upstream DoT servers for redundancy
- Use
serve-expiredto return stale cache entries if upstream is temporarily unavailable - Verify TLS certificate validation is working by testing with an invalid certificate