🧠 How I Added Fallback DNS to My Pi-hole + Unbound Setup (Without Sacrificing Security)
I recently ran into an issue with my Pi-hole setup that uses Unbound as a recursive DNS resolver. Everything worked great—until some domains started failing to resolve.
🧩 The Problem
Certain domains were timing out or returning SERVFAIL, even though they worked fine when using public DNS like 1.1.1.1 or 8.8.8.8. These failures were often caused by:
🧨 Broken DNSSEC validation
⛓️ Misconfigured nameservers or delegation
🛑 Recursive timeout failures
I didn’t want to disable DNSSEC completely, so I looked for a smarter fallback solution.
✅ The Goal
Use Unbound recursion by default (for privacy, speed, and DNSSEC)
Fallback to public DNS if recursion fails
Keep Pi-hole clean, using just one upstream: 127.0.0.1#5335
🛠️ The Fix: Add Fallback to Unbound
I edited the file:
/etc/unbound/unbound.conf.d/pi-hole.conf
And added this at the end:
server:
# ... (existing settings)
# Accept responses even if DNSSEC validation fails
val-permissive-mode: yes
# Fallback to public DNS if recursion fails
forward-zone:
name: "."
forward-first: yes
forward-addr: 1.1.1.1
forward-addr: 8.8.8.8
Then I restarted Unbound:
sudo systemctl restart unbound
🔍 How It Works
Unbound first tries full DNS recursion (using root servers)
If that fails (timeout, broken delegation, etc.), it falls back to 1.1.1.1 or 8.8.8.8
val-permissive-mode: yes lets it accept results even with DNSSEC issues
🧪 Test It
Before:
dig example-broken-domain.com @127.0.0.1 # ❌ Times out or fails
After:
dig example-broken-domain.com @127.0.0.1 # ✅ Returns valid IP (fallback used)
🔐 Notes on Security
DNSSEC is still enforced when possible
Only if validation fails will fallback or permissive results be accepted
You can optionally enable logging (verbosity: 1) to monitor fallback usage
🎯 TL;DR
If you’re running Pi-hole + Unbound and want:
Private, recursive DNS
Resiliency against broken DNSSEC or delegation
Failover to Cloudflare/Google without modifying Pi-hole’s upstreams
Then this config is for you. Works like a charm. 🔧💨
Let me know if you’d like to apply fallback only for specific TLDs (like .xyz or .co.uk) — that’s also possible.
No, it's the other way round:
While defining a forward zone will turn off recursion in general, setting forward-first: yes instructs unbound to resolve DNS requests first by forwarding to 1.1.1.1 or 8.8.8.8, and only if that fails, it would fall back to recursive resolution.
forward-first: <yes or no>
If a forwarded query is met with a SERVFAIL error, and this option is enabled, Unbound will fall back to normal recursive resolution for this query as if no query forwarding had been specified.
Default: no
EDIT:
Setting val-permissive-mode: yes would have unbound serve replies that failed DNSSEC validation, i.e. Pi-hole would receive invalid replies.
It only makes sense to use that if you did configure Pi-hole to do DNSSEC validation:
In this mode DNSSEC is not enforced at all. If it fails validation the result is still passed on to the client.
From man unbound.conf
The security checks are performed, but if the result is bogus (failed security), the reply is not withheld from the client with SERVFAIL as usual. The client receives the bogus data.
Port 5353 is reserved for mDNS usage, so your python proxy should use another port, and 0.3 seconds is a rather short timeout. unbound defaults to dropping DNS requests if recursion takes longer than 1.9 seconds.
To prevent clients from using invalid DNS replies, your python proxy should use upstreams that support DNSSEC and either validate their replies itself, or pass them on to Pi-hole in the same way as unbound's val-permissive-mode: yes would, so Pi-hole can do DNSSEC validation.