Using Unbound as a Secure Local DNS Resolver for VPNs

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

  1. Connect the VPN.
  2. 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.
  3. 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/::1 unless 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 proper tls-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

  1. Install Unbound and configure server: hardening + DoT forwarding (as above).
  2. Bind to 127.0.0.1 only; configure OpenVPN/WireGuard client to set DNS to 127.0.0.1 on connect.
  3. Block outbound port 53/853 for all processes except Unbound itself.
  4. Test with dig @127.0.0.1 example.com +dnssec; run leak tests.
  5. 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</

1 Comment

  1. Avatar for editor1 HJ Duggen

    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?

Leave a Reply

Your email address will not be published. Required fields are marked *