We earn commissions using affiliate links.
Unbound is a validating, recursive, and caching DNS resolver known for its clean codebase, robust security posture, and flexible configuration. When paired with a VPN, Unbound can ensure that all name resolution stays private, authenticated, and routed exactly where you intend—preventing ISP interception, captive proxy tampering, and the dreaded DNS leak.
This guide shows how to deploy Unbound as your local resolver, enable DNSSEC, use DNS-over-TLS (DoT) for encrypted forwarding when you choose to forward, wire it into your VPN so queries never bypass the tunnel, and verify everything with the right tests and observability. We’ll also cover nftables/iptables rules to dead-end any stray DNS outside the resolver.
Important capability note: Unbound supports upstream DNS-over-TLS (DoT) when forwarding and can serve DNS-over-HTTPS (DoH) to downstream clients; however, Unbound does not act as a DoH client for upstream forwarding (use DoT upstream, or place a DoH stub like dnscrypt-proxy / cloudflared in front). :contentReference[oaicite:0]{index=0}
Why Unbound for a VPN-backed Resolver?
| Capability | What it gives you | Why it matters with a VPN |
|---|---|---|
| DNSSEC validation | Cryptographically verifies DNS answers | Prevents upstream tampering, works even over untrusted networks |
| Caching & recursion | Local cache, fast responses; can resolve directly from roots | Lower latency over VPN; fewer third-party dependencies |
| Encrypted forwarding (DoT) | Encrypts hop from Unbound to chosen forwarders | Stops ISP/edge sniffing; evades DNS hijacking/rewrites |
| DoH/DoT downstream service | Optionally serve encrypted DNS to your LAN clients | Protects clients on Wi-Fi segments you don’t fully trust |
| Policy & privacy knobs | qname minimization, hardening, ECS control | Reduces data exposure to upstreams and authorities |
Unbound is developed by NLnet Labs and supports DoT/DoH features (downstream) and modern privacy extensions. :contentReference[oaicite:1]{index=1}
Install Unbound
Ubuntu/Debian
sudo apt update sudo apt install -y unbound ca-certificates
RHEL/CentOS/Rocky
sudo dnf install -y unbound ca-certificates
Verify
unbound -V sudo systemctl enable --now unbound sudo systemctl status unbound
Choose a Mode: Pure Recursive vs. Forwarding Over DoT
Unbound can either fully recurse (query roots, TLDs, and authorities) or forward to one or more upstream resolvers. For many VPN users:
- Pure recursive = maximal independence and privacy; slightly more first-query latency; no dependency on third-party resolvers.
- Forwarding over DoT = encrypted hop to a privacy-respecting resolver (e.g., Quad9, Cloudflare) and often faster cold lookups.
You can do either—or both (e.g., forward for most names, recurse for special domains).
Baseline Hardened Configuration
Edit /etc/unbound/unbound.conf.d/local.conf (or /etc/unbound/unbound.conf on some distros):
server: # Listen only on localhost (adjust if serving LAN) interface: 127.0.0.1 interface: ::1 port: 53 access-control: 127.0.0.0/8 allow access-control: ::1 allow # DNSSEC validation (root trust anchor auto-managed) auto-trust-anchor-file: "/var/lib/unbound/root.key" # Privacy & hardening hide-identity: yes hide-version: yes qname-minimisation: yes harden-glue: yes harden-dnssec-stripped: yes harden-below-nxdomain: yes harden-algo-downgrade: yes prefetch: yes prefetch-key: yes aggressive-nsec: yes # Performance num-threads: 2 cache-min-ttl: 60 cache-max-ttl: 86400 rrset-cache-size: 256m msg-cache-size: 128m # Use system trust for TLS (needed if you enable DoT forwarding) tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
Note: The auto-trust-anchor-file approach is the recommended way to keep DNSSEC trust anchors current. :contentReference[oaicite:2]{index=2}
Forwarding Over DNS-over-TLS (DoT) — Correct, Verified Syntax
To forward to DoT resolvers, enable forward-tls-upstream: yes and pin the expected TLS name using the @port#name notation on each forward-addr. This validates the server certificate against the hostname and prevents silent MITM.
forward-zone: name: "." forward-tls-upstream: yes # Cloudflare DoT (IPv4/IPv6), pinned with SNI hostname forward-addr: 1.1.1.1@853#cloudflare-dns.com forward-addr: 1.0.0.1@853#cloudflare-dns.com forward-addr: 2606:4700:4700::1111@853#cloudflare-dns.com forward-addr: 2606:4700:4700::1001@853#cloudflare-dns.com # Quad9 DoT (malware-filtered) # forward-addr: 9.9.9.9@853#dns.quad9.net # forward-addr: 149.112.112.112@853#dns.quad9.net
Key pieces of this syntax (forward-tls-upstream, ip@853#hostname, tls-cert-bundle) are documented and widely referenced by the Unbound community. :contentReference[oaicite:3]{index=3}
DoH upstream? Unbound does not forward upstream via DoH; use DoT upstream or place a DoH stub in front of Unbound if you must use 443/HTTP2 to the resolver. :contentReference[oaicite:4]{index=4}
Serving DoT/DoH downstream (Optional)
If you also want clients on your LAN to use encrypted DNS to Unbound, you can configure Unbound to listen on a DoT or DoH port and present a certificate (from your internal CA or ACME).
server: # DoT (TCP 853) for downstream clients tls-service-key: "/etc/unbound/private.key" tls-service-pem: "/etc/unbound/fullchain.pem" tls-port: 853 # DoH (TCP 443) for downstream clients (Unbound 1.14+ with HTTPS support) https-port: 443 http-endpoint: "/dns-query"
Downstream DoH/DoT support is covered in Unbound’s documentation; enable only if you need encrypted DNS for your own devices. :contentReference[oaicite:5]{index=5}
Wire It into Your VPN: Leak-Proof DNS
With a VPN, you want all applications to send DNS to 127.0.0.1 (or your router’s Unbound IP) so that resolution is performed locally and egresses through the tunnel exactly as intended. Several patterns work depending on OS and VPN tooling:
OpenVPN client (Linux) + resolvconf
Create an up/down script (e.g., /etc/openvpn/update-resolvconf-unbound.sh):
#!/usr/bin/env bash set -eu IFACE="${dev:-tun0}" if [[ "${script_type:-}" == "up" ]]; then # Route DNS to local Unbound when VPN is up echo "nameserver 127.0.0.1" | resolvconf -a "${IFACE}.unbound" elif [[ "${script_type:-}" == "down" ]]; then resolvconf -d "${IFACE}.unbound" || true fi
In your OpenVPN client config add:
script-security 2 up /etc/openvpn/update-resolvconf-unbound.sh down /etc/openvpn/update-resolvconf-unbound.sh
This ensures DNS queries go to Unbound only while the tunnel is active (and revert otherwise).
systemd-resolved (Ubuntu Desktop)
Bind Unbound to 127.0.0.1:53 and set resolved to use the stub at 127.0.0.53 as consumers normally do; or point NetworkManager’s connection to DNS 127.0.0.1 when the VPN profile is active. Verify with resolvectl status.
Policy: Don’t let anything bypass Unbound
Block direct outbound DNS from clients (except to localhost and, if Unbound forwards over DoT, to the chosen upstream IPs via the VPN interface only).
# nftables example (adjust interface names) table inet filter { chain output { type filter hook output priority 0; # Allow Unbound locally ip daddr 127.0.0.1 udp dport {53,853} accept ip6 daddr ::1 udp dport {53,853} accept # Drop all other DNS from local processes udp dport {53,853} drop tcp dport {53,853} drop } }
For router-side Unbound, enforce similar egress rules at the gateway so LAN devices cannot hit port 53 on the WAN.
Testing the Setup
Basic resolution through Unbound
dig @127.0.0.1 example.com +dnssec +multi
Look for the ad (Authenticated Data) flag which indicates DNSSEC validation. If you enabled DoT forwarding, you should also see upstream connections on 853 when you inspect with ss -tpn or logs.
Confirm no DNS leaks
- Connect the VPN.
- Visit a leak test (ipleak.net). You should see only your VPN egress location’s resolvers, or none if you’re resolving locally with no ECS.
- Temporarily block Unbound or change rules; verify that apps fail rather than silently falling back to public DNS.
Show effective path & TLS pinning
Turn up Unbound logging temporarily and tail the log while running dig queries, or use unbound-control for stats.
Observability & Operations
Enable logs in server: with:
verbosity: 1 logfile: "/var/log/unbound/unbound.log"
Key commands:
# Health and stats sudo unbound-control status sudo unbound-control stats_noreset
Check configuration and reload
sudo unbound-checkconf
sudo unbound-control reload
Use journald/syslog shipping or Prometheus node exporters for basic visibility (queries per second, cache hit ratio inferred from logs, etc.).
Performance Tuning
| Lever | Default/Example | Effect | Notes |
|---|---|---|---|
num-threads |
CPU cores/2 | Parallel lookups | Don’t oversubscribe on tiny systems |
rrset-cache-size, msg-cache-size |
256m / 128m | Cache hit rate | Increase if memory permits |
prefetch, prefetch-key |
on | Proactive refresh | Great for popular domains |
cache-min-ttl, cache-max-ttl |
60 / 86400 | Lower cold-query rate | Don’t violate TTL policy excessively |
Security Hardening Checklist
- Run as non-root/chroot (default packaging does this); drop capabilities.
- Limit interfaces to
127.0.0.1/::1unless you intentionally serve LAN. - DNSSEC on with trusted root anchor.
- qname-minimisation and hardening toggles enabled.
- DoT upstream with hostname pinning (
@853#hostname), and a propertls-cert-bundle. :contentReference[oaicite:6]{index=6} - Egress controls so nothing can reach UDP/TCP 53 outside the resolver path.
- Backups of configuration and trust anchor.
Common Pitfalls (and Fixes)
| Symptom | Likely Cause | Fix |
|---|---|---|
| DoT forwarding fails | No tls-cert-bundle; hostname not pinned; firewall blocks 853 |
Set tls-cert-bundle path; use ip@853#hostname; open 853 to upstream. :contentReference[oaicite:7]{index=7} |
| Unexpected public resolvers on leak tests | Apps making direct DNS calls | Force system DNS to 127.0.0.1 during VPN; block outbound 53/853 except localhost |
| DNSSEC failures for some zones | Clock skew; broken zone; middlebox | Check NTP; test with dig +dnssec; inspect logs for bogus reasons |
| Confusion about DoH upstream | Assuming Unbound forwards to https://…/dns-query |
Use DoT for upstream forwarding; DoH upstream is not supported in Unbound. :contentReference[oaicite:8]{index=8} |
Systemd Service & Health
On most distros Unbound ships with a unit file. Basic operations:
sudo unbound-checkconf sudo systemctl restart unbound sudo journalctl -u unbound -n 100 --no-pager
Consider a watchdog that reloads on configuration updates and alerts when unbound-control status fails.
End-to-End Example: VPN-Locked Resolver
- Install Unbound and configure
server:hardening + DoT forwarding (as above). - Bind to 127.0.0.1 only; configure OpenVPN/WireGuard client to set DNS to 127.0.0.1 on connect.
- Block outbound port 53/853 for all processes except Unbound itself.
- Test with
dig @127.0.0.1 example.com +dnssec; run leak tests. - Observe logs and
unbound-control stats_noreset; tune caches if QPS is high.
Conclusion
With Unbound as a local resolver and a VPN handling egress, you gain validated answers (DNSSEC), encrypted forwarding (DoT), tight policy control, and no DNS leaks. Use hostname-pinned DoT for upstream privacy, keep strict egress rules, and verify continuously with logs and tests. If you need DoH for downstream clients, Unbound can provide it; if you need DoH upstream, slot a DoH stub in front of Unbound or stick with DoT (the simpler, standards-track choice).
FAQ: Unbound + VPN
Does Unbound support DNS-over-HTTPS to upstream resolvers?
Unbound can serve DoH downstream to clients, but it does not act as a DoH client for upstream forwarding. Use DoT upstream (recommended) or place a DoH stub (e.g., dnscrypt-proxy/cloudflared) ahead of Unbound.
How do I validate the upstream DoT server’s certificate?
Pin the expected TLS hostname with forward-addr: IP@853#hostname</



Hi! Thanks for the guide. I’m a bit confused now though because I’ve been wondering about DNS & VPN config and found the article linked below which suggests that using private DNS with a VPN makes it MORE likely for DNS leaks, and it is generally a better option to use the VPN’s own DNS.
Specifically “configuring any third-party DNS service on your system makes it more likely DNS requests will be routed outside the VPN tunnel. Private DNS increases the chances of this happening, because encrypting the DNS queries makes it harder for the DNS protection measures used by the VPN software to ensure they’re properly routed through the VPN tunnel.”
Is that a concern with this configuration, and if not, why?