Running a single Pi-hole is fine until the day you reboot it. Then every device on the network loses DNS at once, and you learn something about how much of your house depends on name resolution.
The usual fix is to hand out a second DNS server over DHCP. In my case that turned out to be worse than useless, and the reason is worth writing down: a public resolver cannot be your secondary if outbound port 53 is being intercepted.
Why the obvious secondary does not work
On my network, outbound DNS on port 53 does not reach the internet: it is intercepted and redirected to the gateway, so any device pointed at an external resolver stops getting answers from that resolver. My Pi-hole works around it by resolving over DNS-over-HTTPS on port 443 instead.
So if I hand clients a public resolver as their secondary, I am handing them an address that cannot answer. Worse, clients do not treat a secondary as a cold standby. They will happily race both servers or fall back to the second one on any hiccup, and then queries either time out or get silently answered by the gateway instead of by my Pi-hole. Ad blocking stops working intermittently and nothing looks broken.
Check whether your router belongs in the DHCP DNS list at all. Plenty of gateways get handed out as a secondary by default without actually running a resolver: the connection on port 53 is accepted, then times out after five seconds. Every client that falls back to it pays that stall. It presents as “the network is slow sometimes” rather than as a DNS fault, which makes it one of the harder symptoms to trace back to its cause.
The lesson: a secondary DNS server is only useful if it is actually able to resolve under the same constraints as the primary. Test it directly before you hand it to a single client:
dig @<candidate-ip> example.com +time=2 +tries=1If that does not return an answer, it does not belong in your DHCP scope.
The build: two independent Pi-holes, each with its own DoH tunnel
Since the only thing that resolves on my network is a Pi-hole with DoH upstream, the secondary has to be a second one of exactly that. I put them on separate Proxmox nodes, which is the entire point — a secondary on the same host only protects you from a service crash, not from the reboot or hardware failure you are actually worried about.
Each one runs its own dnscrypt-proxy instance listening on loopback, and Pi-hole points upstream at it:
# /etc/pihole/pihole.toml
upstreams = ["127.0.0.1#5053"]No shared upstream, no shared state, no dependency between the two. Either can die without touching the other.
Two things that bite when you clone the first one
Cloning the container is the fast way to build the second, and it will look like it works while being subtly broken.
1. Conditional forwarding does not travel with the blocklists. If you forward an internal zone to an authoritative server, that config lives in revServers and in /etc/dnsmasq.d/, not in the part of the config people think of as “the Pi-hole setup”. Copy the blocklists only and your clone resolves the internet perfectly while failing every internal hostname. Which of course is the half you notice last.
2. Proxmox may be writing over your own DNS records. This one cost me real time. If the container’s search domain matches the domain you publish services under, Proxmox writes a line into /etc/hosts mapping <hostname>.<searchdomain> to the container’s own IP. That entry wins over the conditional forward, so the resolver confidently answers with itself for one specific name. Set the search domain to something you do not serve records for.
The part I chose not to automate
There is no automatic synchronisation between my two Pi-holes. Tools exist for this. I did not deploy one.
The reason is that blocklist drift is a cosmetic problem — worst case one resolver blocks an ad the other lets through — while a sync daemon is a new failure mode that can push a broken config to both resolvers at once. That trades an occasional annoyance for a correlated failure of the thing I built redundancy for in the first place. I update the lists by hand on both when I change them, which is a few times a year.
Write down the deliberate gaps. Six months later, “no sync between the two Pi-holes” is indistinguishable from a mistake unless the reasoning is recorded somewhere. Undocumented shortcuts get “fixed” by future you, usually at the worst moment.
Verifying it actually failed over
Redundancy you have not tested is a belief, not a property of your system. Shut the primary down and check from a client, not from the server:
# from a normal client, while the primary is off
nslookup example.com
nslookup internal-service.your-internal-zoneBoth have to answer. The second one is the test that matters — it is the one that catches the missing conditional-forward config, and it is the one that fails when you cloned carelessly.
Then check your query logs on the secondary to confirm the traffic really moved there, rather than the gateway quietly answering on its behalf.