NxValue Stack

Unbound Recursive Resolver: An In-Depth Guide

Every time you open a website, send an email or launch an app, a DNS lookup happens first. Something has to turn a name like example.com into an IP address, and that something is a resolver. Unbound is one of the best resolvers you can run yourself: small, fast, security-focused and free.

This guide covers what Unbound is, where it came from, how recursive resolution actually works, why people choose to run it, and how to install, configure, secure, tune, monitor and troubleshoot it. It also explains how Unbound fits next to Pi-hole and other tools, and where it is the wrong choice.

What is Unbound?

Unbound is an open-source validating, recursive, caching DNS resolver. Each of those words matters:

Unbound is written in C, released under the permissive BSD licence, and runs on Linux, the BSDs, macOS, Windows and most things in between. It is built for clients, home networks, ISPs and enterprises alike.

Where Unbound comes from

Unbound was created by NLnet Labs, a Dutch not-for-profit that has built a lot of core internet infrastructure software, including the NSD authoritative server. The first release arrived in 2008 as a collaboration between NLnet Labs, Verisign, Nominet and Kirei. The goal was a modern resolver with security and performance as design priorities rather than features bolted on later.

That history explains its reputation. Unbound has a compact codebase, a conservative approach to security, and a long record of careful maintenance. It is also the resolver many operating systems and appliances chose as their default:

How recursive resolution works

To understand why Unbound exists, it helps to follow one lookup from start to finish. Say your laptop asks for www.example.com and nothing is cached.

  1. Your device asks a stub resolver, the small piece of code built into the operating system. The stub doesn't do the hard work. It just sends the question to the resolver it has been configured with. That is where Unbound sits.
  2. Unbound checks its cache. If it has a fresh answer, it replies immediately and the story ends here.
  3. Unbound asks a root server. It starts from a built-in list of root server addresses, the root hints. The root servers don't know the answer, but they know who runs .com, so they send back a referral to the .com servers.
  4. Unbound asks a .com server. That server doesn't know the address either, but it knows which name servers are responsible for example.com, and refers Unbound to them.
  5. Unbound asks the authoritative server for example.com. This one holds the real record and returns the IP address for www.example.com.
  6. Unbound validates and caches. If the zone is signed, Unbound checks the DNSSEC signatures up the chain. Then it stores the answer and returns it to your device.

Every step is also cached. After the first lookup, Unbound already knows where the .com servers are and who runs example.com, so the next lookup under that domain skips most of the journey. In a busy network, most queries are answered straight from the cache.

Recursive resolving versus forwarding

This distinction is the heart of why many people install Unbound.

A forwarding resolver hands your questions to a bigger upstream resolver, such as your ISP's or a public one, and relays the answer. It is simple and often fast, but that upstream sees every domain you look up, and you have to trust what it tells you.

A recursive resolver like Unbound goes to the authoritative servers directly. No single company sees your complete lookup history, because each authoritative server only sees the questions about the domains it is responsible for. You trade a little first-lookup speed for independence.

Unbound can do either, or both at once. You can recurse by default and forward specific zones, such as a corporate domain, to specific servers.

Why run your own recursive resolver?

There are practical reasons, and they are worth weighing honestly.

The honest downsides: the first lookup of a cold name is slower than asking a big cached public resolver, you take on the job of keeping it patched and running, and if your connection filters or breaks direct DNS to authoritative servers, recursion can fail where forwarding would work.

Key features of Unbound

Installing Unbound

Package names are simple on most systems.

On Debian and Ubuntu:

sudo apt update
sudo apt install unbound

On Fedora, RHEL and derivatives:

sudo dnf install unbound

On Alpine Linux:

apk add unbound

On macOS with Homebrew:

brew install unbound

On FreeBSD it is already in the base system, and on OpenBSD it is the default resolver. Docker images exist too, though for a resolver that other devices depend on, a plain package install is usually simpler to keep reliable.

Two helper tools usually come with the package:

One common obstacle on Linux: another service may already be using port 53. On Ubuntu, systemd-resolved runs a local stub listener there. Either set Unbound to listen on a different address or port, or disable that stub listener. Check with sudo ss -lntup | grep ':53'.

The configuration file

The main file is usually /etc/unbound/unbound.conf, and distributions often include extra files from a directory such as /etc/unbound/unbound.conf.d/. Configuration is organised in blocks, and the most important one is server:. A good configuration for a home or small-office resolver looks like this:

server:
    # Listening
    interface: 0.0.0.0
    port: 53
    do-ip4: yes
    do-ip6: yes
    do-udp: yes
    do-tcp: yes

    # Who may use this resolver
    access-control: 127.0.0.0/8 allow
    access-control: 192.168.0.0/16 allow
    access-control: 10.0.0.0/8 allow

    # Privacy of the server itself
    hide-identity: yes
    hide-version: yes
    qname-minimisation: yes

    # Hardening
    harden-glue: yes
    harden-dnssec-stripped: yes
    harden-referral-path: yes
    use-caps-for-id: no
    unwanted-reply-threshold: 10000000

    # Performance
    num-threads: 2
    msg-cache-size: 64m
    rrset-cache-size: 128m
    prefetch: yes
    prefetch-key: yes
    edns-buffer-size: 1232

    # Resilience
    serve-expired: yes
    serve-expired-ttl: 86400
    serve-expired-client-timeout: 1800

Treat this as a starting point and run unbound-checkconf before applying it. Defaults differ slightly between versions and distributions, so confirm option names against the unbound.conf manual page for what you have installed.

What the important options do

DNSSEC with Unbound

DNSSEC adds digital signatures to DNS data so a resolver can prove an answer is genuine and unmodified. Unbound validates by following a chain of trust from a trust anchor, the root zone's public key, down through each delegation.

Setup is usually automatic. A line such as the following points Unbound at a root trust anchor file that unbound-anchor maintains:

server:
    auto-trust-anchor-file: "/var/lib/unbound/root.key"

Distribution packages often configure this for you, so check before adding it twice.

Understand how results look. A validated answer carries the AD (Authenticated Data) flag. An answer that fails validation is returned as SERVFAIL, not as a warning, because the whole point is to refuse forged data. You can check with dig:

dig +dnssec example.com @127.0.0.1
dig sigfail.verteiltesysteme.net @127.0.0.1
dig sigok.verteiltesysteme.net @127.0.0.1

The sigfail test domain is deliberately broken, so a validating resolver should return SERVFAIL for it, while sigok should resolve with the ad flag set.

Two things trip people up. First, DNSSEC signatures have validity periods, so a machine with a badly wrong clock can reject perfectly good data. Keep time synchronised. Second, a domain whose owner has misconfigured DNSSEC will fail to resolve through a validating resolver even though it works elsewhere. That is DNSSEC working as designed, but it can look like Unbound is at fault.

Encrypted DNS with Unbound

There are two separate directions to think about.

Encrypting traffic to your upstream (forwarding over TLS)

If you choose to forward rather than recurse, you can at least encrypt the hop to the upstream resolver:

server:
    tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt

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

The part after # is the name Unbound checks the server's certificate against. Forwarding like this protects the path from eavesdroppers, but the upstream provider still sees your queries, so it is a different privacy trade-off from true recursion.

Serving DoT and DoH to your own devices

Unbound can also be the encrypted endpoint that your phone or laptop connects to. For DoT you provide a certificate and key and open the TLS port:

server:
    interface: 0.0.0.0@853
    tls-service-key: "/etc/letsencrypt/live/dns.example.com/privkey.pem"
    tls-service-pem: "/etc/letsencrypt/live/dns.example.com/fullchain.pem"
    tls-port: 853

For DoH, Unbound listens on an HTTPS port with the same certificate. It requires a build with HTTP/2 support, so check that your package has it:

server:
    interface: 0.0.0.0@443
    https-port: 443
    tls-service-key: "/etc/letsencrypt/live/dns.example.com/privkey.pem"
    tls-service-pem: "/etc/letsencrypt/live/dns.example.com/fullchain.pem"

Three practical warnings. You need a real certificate from a public certificate authority, because phones and browsers reject self-signed ones. You need a hostname that points at your server and ports reachable from where your devices roam. And you must keep access-control tight, because an encrypted endpoint open to the whole internet is still an open resolver, and attackers can use it for amplification attacks. Many people put a dedicated front end such as dnsdist or nginx in front of Unbound to handle TLS and authentication, and let Unbound do only what it is best at.

Local zones, local data and split DNS

Unbound can answer for names that exist only on your network.

server:
    local-zone: "home.lan." static
    local-data: "nas.home.lan.      IN A 192.168.1.20"
    local-data: "printer.home.lan.  IN A 192.168.1.30"
    local-data-ptr: "192.168.1.20 nas.home.lan"

The static zone type answers authoritatively from your data and returns NXDOMAIN for anything else in that zone. Other zone types change the behaviour: transparent serves your data but lets other names resolve normally, and always_nxdomain or always_refuse are used for blocking.

Blocking a single domain is as simple as:

server:
    local-zone: "ads.example.net" always_nxdomain

That works for small lists, but it is not a good way to manage tens of thousands of domains. For large blocklists, a dedicated tool such as Pi-hole in front is far easier to maintain.

Split-horizon DNS is the same idea applied to one name that should resolve differently inside and outside your network, for example returning a private address for app.example.com to internal clients. Local data makes that straightforward.

Stub zones, forward zones and private domains

Real networks often have domains that public DNS can't answer. Unbound handles them in two ways.

forward-zone:
    name: "corp.example.com."
    forward-addr: 10.0.0.53

Two related settings matter for security. private-address tells Unbound to discard public answers that contain private IP ranges, which blocks a DNS rebinding trick where a malicious public domain points at your internal devices. private-domain exempts specific domains from that rule so your own internal names still work.

Running Unbound with Pi-hole

Pi-hole is a network-wide ad and tracker blocker that works by answering blocked domains with a null response. Pi-hole is not a recursive resolver itself. It needs an upstream to ask for everything that isn't blocked, and that is where Unbound fits neatly.

The usual arrangement keeps each tool in its own lane:

  1. Clients send DNS queries to Pi-hole.
  2. Pi-hole filters against its blocklists and logs the query.
  3. Anything allowed is forwarded to Unbound on the same machine.
  4. Unbound recurses, validates and caches, then returns the answer.

Because both run on one box, Unbound listens only on localhost with an unusual port so it doesn't collide with Pi-hole on port 53:

server:
    verbosity: 0
    interface: 127.0.0.1
    port: 5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes
    do-ip6: no
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: no
    edns-buffer-size: 1232
    prefetch: yes
    num-threads: 1
    private-address: 192.168.0.0/16
    private-address: 10.0.0.0/8
    private-address: 172.16.0.0/12

Then, in Pi-hole's DNS settings, remove the public upstream servers and add 127.0.0.1#5335 as a custom upstream. Restart both services and test:

dig example.com @127.0.0.1 -p 5335

A few notes on this pairing. Pi-hole's logs will show every client, while Unbound only sees Pi-hole, which is normal. Both layers can do DNSSEC, and running validation in Unbound is enough. And the first visit to a never-seen domain will be a little slower than with a big public upstream, then fast thereafter, because Unbound's cache warms up.

Performance tuning

Unbound is fast by default. For busy networks or small hardware, these are the knobs that matter.

Measure before and after. unbound-control stats_noreset reports cache hit rate and latency distribution, and a hit rate in the 70 to 90 percent range is normal for a busy household.

Security and hardening

A resolver is a piece of internet-facing infrastructure the moment it listens beyond localhost, so treat it that way.

Day-to-day management with unbound-control

The control utility lets you work with a live server. First enable it in unbound.conf:

remote-control:
    control-enable: yes
    control-interface: 127.0.0.1

Then set up its keys if your package hasn't done so, with unbound-control-setup. Useful commands include:

For graphs, a Prometheus exporter for Unbound is available and pairs well with Grafana, which is useful for spotting query spikes and falling hit rates.

Troubleshooting

Most Unbound problems fall into a small number of patterns.

For deeper debugging, raise verbosity from 0 up to 2 or 3 temporarily, or use log-queries: yes while testing. Turn both off afterwards, since they produce a lot of output and logging queries has privacy implications.

Unbound versus other resolvers

If you want a straightforward, well-documented, low-maintenance recursive resolver with excellent defaults, Unbound is hard to beat.

What Unbound will not do

Honest limits matter, because the most common disappointment comes from expecting more privacy than DNS can deliver.

Frequently asked questions

Is Unbound free? Yes. It is open source under the BSD licence, and there is no paid edition required to use it.

Is Unbound a DNS server? It is a DNS resolver. A DNS server in the authoritative sense is something else, and Unbound deliberately does not do that job, so it stays small and focused.

Do I need Unbound if I use Pi-hole? No. Pi-hole works with any upstream. Unbound is an option that gives you recursion and local validation instead of depending on an outside upstream.

Does Unbound make browsing faster? Repeat lookups are faster because they come from a local cache. A cold lookup can be slower than a large public resolver that has already cached the name. With prefetching and a warm cache, the day-to-day experience is usually as fast or faster.

Is it safe to expose Unbound to the internet? Only with strict access control, rate limiting, and a clear reason. For almost every home user, keep it on the local network and reach it remotely through a VPN or a properly secured encrypted endpoint.

Can Unbound run on a Raspberry Pi? Yes, easily. It uses very little memory and CPU, which is why it is so popular on small boards.

Does it support IPv6? Yes, for clients and for talking to upstream servers. If your IPv6 connectivity is unreliable, disable it with do-ip6: no rather than living with timeouts.

How often should I update it? Whenever your distribution ships security updates, and at least every few months. Check the release notes for new features like improved encrypted DNS support.

Why did DNSSEC validation break a site that works elsewhere? Because the domain's DNSSEC records are wrong or expired, and your resolver is correctly refusing them. Test with dig +cd to confirm, and contact the domain owner if it is their mistake.

Summary

Want the benefits without the build?

Setting up Unbound properly is very doable, and this guide gives you everything needed to do it. But a resolver people depend on is also a small ongoing job: certificates to renew, ports to expose safely, updates to apply, and a phone that stops loading pages whenever something quietly breaks.

If you would rather just have it working, NxValue Stack provisions a private Pi-hole/AdGuard Home + Unbound node for you, with DoH and DoT endpoints already generated. You copy a hostname into your device settings instead of building the stack, and it works anywhere your phone has a connection, not only on your home network.

Skip the setup

Get your own private Pi-hole node with DoH and DoT endpoints. Billed hourly, cancel anytime.