High Availability Pi-hole
I recently came across a blog post about making your DNS redundant using Pi-hole, Unbound, Nebula-Sync and Keepalived. I somewhat replicated the setup and it was pretty straight forward, but when I tested it by rebooting one of my Raspberry Pis running Pi-hole it didn't work. After some research and honestly asking an LLM I finally understood how Keepalived works and why my setup, as well as the setup described in the blog post simply wouldn't work.
I'm not going through the whole setup of installing Pi-hole, Unbound, Nebula-Sync and Keepalived because that's done very detailed in the original blog post.
The main issue is that Keepalived doesn't use a redirect from a virtual IP to the actual IP instead both IPs exist simultaneously. When a DNS request is sent to the virtual IP it isn't redirected to the actual IP of my Pi-hole. But the virtual IP effectively is the IP of my (primary) Pi-hole. The problem is that Pi-hole must be configured to listen on both its native IP and the virtual IP or otherwise it simply won't receive the requests - same goes for Unbound.
Configuring Pi-hole
To make Pi-hole listen on both IPs I had to go to the web GUI -> Settings -> DNS -> enable the "Expert" settings toggle and check "Permit all origins".

Pi-hole applies the settings immediately.
Configuring Unbound
To make Unbound listen on both IPs I had to edit the unbound.conf . On my Raspberry Pi 2 running Unbound on bare-metal it's located at /etc/unbound/unbound.conf or /etc/unbound/unbound.conf.d/pi-hole.conf. On my Raspberry Pi 4 running Unbound inside a container it's located inside the respective Docker volume.
In the config file I simply replaced the interface: 127.0.0.1 with interface: 0.0.0.0 to make Unbound listen on all interfaces. Finally I restarted the systemd service on my Raspi 2 and the Docker container on my Raspi 4.
Configuring Keepalived
The Keepalived config in the original blog post is also not ideal because it doesn't use the preempt option. Enabling preempt ensures that if the primary node goes offline and the backup takes over, the primary node will reclaim its role once it comes back online. Without preempt the backup and primary nodes would effectively swap roles permanently. While this isn’t strictly necessary, if you’ve configured your Pi-hole nodes with a clear primary and secondary hierarchy in mind, I recommend enabling preempt. If you're running two or more identical instances of Pi-hole it's best to disable it.
Testing the setup
Now to test the setup I opened both Pi-hole's dashboards to monitor live queries. I then rebooted the Raspi 2 and watched the queries on its dashboard slowly coming to a halt while the Raspi 4's dashboard shows that it successfully took over and received incoming queries.