When Pi-hole + Unbound Stop Resolving: A DNSSEC Trust Anchor Fix

I have my own private DNS setup in my home network, powered by Pi-hole running on my very first Raspberry Pi, a humble Model B Rev 2. It’s been quietly handling ad-blocking and DNS resolution for years. But today, something broke.

I noticed that none of my devices could resolve domain names. Pi-hole’s dashboard looked fine. The DNS service was running, blocking was active, but every query failed. Even direct dig queries returned SERVFAIL. Here’s how I diagnosed and resolved the issue.

The Setup

My Pi-hole forwards DNS queries to Unbound, a recursive DNS resolver running locally on port 5335. This is configured in /etc/pihole/setupVars.conf.

PIHOLE_DNS_1=127.0.0.1#5335
PIHOLE_DNS_2=127.0.0.1#5335

And my system’s /etc/resolv.conf points to Pi-hole itself

nameserver 127.0.0.1

Unbound is installed with the dns-root-data package, which provides root hints and DNSSEC trust anchors:

$ dpkg -l dns-root-data|grep ^ii
ii dns-root-data 2024041801~deb11u1 all DNS root hints and DNSSEC trust anchor

The Symptoms

Despite everything appearing normal, DNS resolution failed:

$ dig google.com @127.0.0.1 -p 5335

;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL

Even root-level queries failed:

$ dig . @127.0.0.1 -p 5335

;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL

Unbound was running and listening:

$ netstat -tulpn | grep 5335

tcp 0 0 127.0.0.1:5335 0.0.0.0:* LISTEN 29155/unbound

And outbound connectivity was fine. I pinged one of the root DNS servers directly to ensure this:

$ ping -c1 198.41.0.4 
PING 198.41.0.4 (198.41.0.4) 56(84) bytes of data.
64 bytes from 198.41.0.4: icmp_seq=1 ttl=51 time=206 ms

--- 198.41.0.4 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 205.615/205.615/205.615/0.000 ms

The Diagnosis

At this point, I suspected a DNSSEC validation failure. Unbound uses a trust anchor, which is simply a cryptographic key stored in root.key. This cryptographic key is used to verify the authenticity of DNS responses. Think of it like a passport authority: when you travel internationally, border agents trust your passport because it was issued by a recognized authority. Similarly, DNSSEC relies on a trusted key at the root of the DNS hierarchy to validate every response down the chain. If that key is missing, expired, or corrupted, Unbound can’t verify the authenticity of DNS data — and like a border agent rejecting an unverified passport, it simply refuses to answer, returning SERVFAIL.

Even though dns-root-data was installed, the trust anchor wasn’t working.

The Fix

I regenerated the trust anchor manually:

$ sudo rm /usr/share/dns/root.key
$ sudo unbound-anchor -a /usr/share/dns/root.key
$ sudo systemctl restart unbound

After this, Unbound started resolving again:

$ dig google.com @127.0.0.1 -p 5335

;; ->>HEADER<<- opcode: QUERY, status: NOERROR
;; ANSWER SECTION:
google.com. 300 IN A 142.250.195.78

Why This Happens

Even with dns-root-data, the trust anchor could become stale — especially if the system missed a rollover event or the file was never initialized. Unbound doesn’t log this clearly, so it’s easy to miss.

Preventing Future Failures

To avoid this in the future, I added a weekly cron job to refresh the trust anchor:

0 3 * * 0 /usr/sbin/unbound-anchor -a /usr/share/dns/root.key

And a watchdog script to monitor Unbound health:

$ dig . @127.0.0.1 -p 5335 | grep -q 'status: NOERROR' || systemctl restart unbound

This was a good reminder that even quiet systems need occasional maintenance. Pi-hole and Unbound are powerful together, but DNSSEC adds complexity. If you’re running a similar setup, keep an eye on your trust anchors, and don’t trust the dashboard alone.

This entry was posted in FLOSS, RaspberryPi and tagged , , , , , , . Bookmark the permalink.

Leave a Reply