How to Set Up Unbound as a Recursive DNS Resolver on Raspberry Pi

How to Set Up Unbound as a Recursive DNS Resolver on Raspberry Pi

When you type a domain name into your browser, your device sends a DNS query to a resolver to find the corresponding IP address. Most people rely on third-party resolvers like Google (8.8.8.8) or Cloudflare (1.1.1.1) for this, but that means handing over your entire browsing history to those providers. Unbound lets you run your own recursive DNS resolver that queries the authoritative name servers directly, keeping your DNS lookups private.


What is Unbound?

Unbound is a lightweight, open-source DNS resolver developed by NLnet Labs. When configured as a recursive resolver, it does not forward queries to an upstream provider. Instead, it starts at the DNS root servers and works its way down the hierarchy to resolve each query. This means:

  • No third-party DNS provider sees your queries
  • You get DNSSEC validation out of the box
  • Responses are cached locally for faster repeat lookups
  • You have full control over your DNS resolution chain

Prerequisites

Before you begin, make sure you have:

  • A Raspberry Pi (any model) running Raspberry Pi OS
  • SSH access or a terminal session on the Pi
  • Root or sudo privileges
  • Port 53 available (if Pi-hole is running, we will configure it to use Unbound as upstream later)

Step 1: Update Your System

Bash
sudo apt update && sudo apt upgrade -y

Step 2: Install Unbound

Install Unbound from the default Raspberry Pi OS repositories:

Bash
sudo apt install -y unbound

Unbound will be installed and started automatically. However, it may fail to start initially if port 53 is already in use. That is fine -- we will configure it properly before restarting.


Step 3: Download Root Hints

The root hints file tells Unbound where to find the DNS root servers. Download the latest version:

Bash
sudo wget -O /var/lib/unbound/root.hints https://www.internic.net/domain/named.root

It is good practice to update this file periodically. Add a monthly cron job with sudo crontab -e:

Code
0 3 1 * * wget -O /var/lib/unbound/root.hints https://www.internic.net/domain/named.root && systemctl restart unbound

Step 4: Create the Unbound Configuration

Create a custom configuration file:

Bash
sudo nano /etc/unbound/unbound.conf.d/pi-config.conf

Paste the following configuration:

YAML
server:
    verbosity: 0
    interface: 0.0.0.0
    port: 5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes
    do-ip6: no
    prefer-ip6: no
    root-hints: "/var/lib/unbound/root.hints"
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: no
    edns-buffer-size: 1232
    prefetch: yes
    num-threads: 1
    so-rcvbuf: 1m
    private-address: 192.168.0.0/16
    private-address: 169.254.0.0/16
    private-address: 172.16.0.0/12
    private-address: 10.0.0.0/8
    private-address: fd00::/8
    private-address: fe80::/10

Key settings: port 5335 avoids conflicts with Pi-hole on port 53 (change to 53 if running standalone). harden-glue and harden-dnssec-stripped enable DNSSEC validation. prefetch refreshes cached entries before expiry. The private-address entries prevent DNS rebinding attacks.


Step 5: Test the Configuration and Restart Unbound

Verify the configuration, restart Unbound, and check its status:

Bash
sudo unbound-checkconf /etc/unbound/unbound.conf.d/pi-config.conf
sudo systemctl restart unbound
sudo systemctl status unbound

The first command should report no errors. The status command should show Unbound as active and running.


Step 6: Test DNS Resolution

Use the dig command to verify Unbound is resolving queries correctly. Query Unbound directly on port 5335:

Bash
dig example.com @127.0.0.1 -p 5335

You should see an ANSWER SECTION with the IP address for example.com and status: NOERROR in the header. The first query may be slower as Unbound builds its cache.

Test DNSSEC validation with these two commands:

Bash
dig sigfail.verteiltesysteme.net @127.0.0.1 -p 5335
dig sigok.verteiltesysteme.net @127.0.0.1 -p 5335

The first should return status: SERVFAIL (invalid DNSSEC signature correctly rejected). The second should return status: NOERROR (valid signature accepted).


Step 7: Integrate with Pi-hole

If you are running Pi-hole on the same Raspberry Pi, you can configure it to use Unbound as its upstream DNS resolver. This gives you the best of both worlds: Pi-hole handles ad blocking and Unbound handles recursive resolution. For Pi-hole installation instructions, see our Pi-hole guide.

  1. Open the Pi-hole admin interface
  2. Go to Settings then DNS
  3. Uncheck all upstream DNS servers (Google, Cloudflare, etc.)
  4. Under Custom 1 (IPv4), enter 127.0.0.1#5335
  5. Click Save

Pi-hole will now send all its DNS queries to your local Unbound instance instead of a third-party resolver.


Troubleshooting

  • Unbound fails to start: Run sudo unbound-checkconf to find configuration errors. Also check journalctl -u unbound for detailed error messages.
  • Port conflict on 53: If another service is using port 53, keep Unbound on port 5335 and configure clients or Pi-hole to point to that port.
  • Slow first queries: This is normal. Unbound needs to traverse the DNS hierarchy from the root servers on the first lookup. Subsequent queries for the same domain are served from cache.
  • dig command not found: Install the dnsutils package with sudo apt install -y dnsutils.
  • Pi-hole not using Unbound: After changing the upstream DNS in Pi-hole, restart the Pi-hole DNS service with pihole restartdns to ensure the changes take effect.

Conclusion

Running Unbound as a recursive DNS resolver on your Raspberry Pi gives you full control over your DNS resolution. Combined with Pi-hole for ad blocking, you get a private and efficient DNS setup that does not depend on any third-party provider. The entire DNS chain -- from root servers to your browser -- stays under your control.