High Availability Pi-hole 6

After a long time, a totally new under the hood, Pi-hole is here. This is an update to my old post, A redundant DNS - Pi-Stack. This HA DNS will use Pi-hole, Unbound, Nebula-sync and keepalived.

High Availability Pi-hole 6

Why a High Availability DNS?

Because without DNS, nothing works. In corporate networks, infrastructure servers, like DNS, are run as HA or at least redundant by number. Usually, this is a feature of the app. But, we will do it with the humble Pi-hole.

How will this all work?

  • Running 3 Pi-hole servers on different nodes, one preferably on real rust like a Raspberry Pi. They will use Unbound.
  • One will be the master (192.0.2.53) from where all data is synchronized to the other two. Enter on one, use on many, performed by nebula-sync.
  • The other two are used as a HA pair using one external IP 192.02.54. The HA Master is 192.0.2.55 and .192.02.56 will act as the HA Backup. Powered by keepalived

Upgrading a standalone Pi-hole 5 → 6

Upgrading to Pi-hole v6 should be straightforward. For existing users, we recommend backing up your current configuration before proceeding, as the upgrade is strictly a one-way operation.

As always, you can upgrade using the command pihole -up on the terminal.

During the upgrade operation, you will be presented with a dialog box, asking if you wish to disable lighttpd. Doing so is probably appropriate for most users.
If you host other web pages than Pi-hole’s, you may choose to keep it enabled.

IF lighttpd is disabled, pihole-FTL will attempt to bind to ports 80 and 443.
If there is any conflict on these ports, it will revert to port 8080 for HTTP.

⚠️
My old post A Redundant DNS uses Gravity-Sync, Gravity-sync isn't maintained anymore. Consider replacing it with Nebula-sync.
It lacked the HA component and relayed on just using many.

Installation types

How to install? It's your choice, as always on Linux, how you do it:

  • on bare metal like a Raspberry Pi
  • a VM for max security
  • a CT for easy installation
  • Docker installation see later to be post: A Docker Based HA Pi-hole 6

Pi-hole version 6 – Docker image

The docker image has undergone a complete rewrite from the ground up, and is now based on Alpine rather than Debian. The same migration scripts that run on bare metal will also run on Docker – your configurations will be migrated to the new format.

The exception to this is environment variables. You can start the container with the old variables in place but don’t expect them to work! It is recommended to read the docker section of our docs page before upgrading.

Fresh Installation – Pi-hole and Unbound

We will be utilizing the following apps:

  • Pi-hole will be our interface for DNS, DHCP and Blocking of ads and bad IPs.
  • Unbound is a validating, recursive, caching DNS resolver. It is designed to be fast and lean and incorporates modern features based on open standards.
    For redundancy and HA, we set up a Pi-hole on each node in the cluster or on several servers. This can also be a good project for all those unused Raspberry Pi everyone has in their drawers.
  • Nebula-sync, synchronize Pi-hole v6.x configuration to replicas. It's not a part of the official Pi-hole project, but uses the API provided by Pi-hole instances to perform the synchronization actions.
  • Keepalived, for high-availability, is achieved by using the VRRP protocol. VRRP is a fundamental brick for router failover.

Create the nodes

Roles by IP

  • 192.0.2.53: The master DNS and DHCP, will be synced to the 2 HA nodes
  • 192.0.2.54: The virtual IP for the HA DNS nodes
  • 192.0.2.55: pi-2 HA master node
  • 192.0.2.56: pi-3 HA node

For this example, we will set up the Pi-holes using a CT. Give it an ID and a name, enter the password and an SSH public key, select your OS, and accept the Disk and CPU as is. Always use SSH keys for security.

If you aren't going to use the Pi-hole for DHCP

  • Set Network to DHCP and we are ready with the setup
  • Set Firewall on
  • Copy the MAC and set up your current DHCP to supply the correct IP and the other stuff a good DHCP can do

If you are using the Pi-hole for DHCP

Yes, we will use the Firewall to block unwanted traffic

Start the first VM 192.0.2.53

Upgrade the CT

apt-get update && apt-get dist-upgrade -y

Install other required parts

apt-get install ca-certificates curl gnupg -y

Add a user and add to sudo ans docker groups

adduser <username>
usermod -ag sudo <username>

Install Pi-hole

First, download the file and rename it.

wget -O pi-install.sh https://install.pi-hole.net 

Carefully read the file, change anything you don't like, and make sure you understand and accept the implications of running this script.

Make it executable, chmod 700 pi-install.sh or chmod +x pi-install.sh

Run the script ./pi-install.sh

  • you will be asked to select your upstream DNS select Custom.
  • I also recommend selecting the default StevenBlack's Unified Hosts List to be installed.
  • Query logging on/off is your choice, read the documentation

Set your own password, pihole setpassword, otherwise use the random one as generated like AbDOlq83

Reboot and all is done for now, reboot now

Check for success

Login at the IP displayed to you, 192.0.2.53/admin

Install Unbound

apt install unbound -y

Configure Unbound

Copy the configuration file from the Unbound documentation and paste it into the configuration file pi-hole.conf. In my cleanup version, IPv6 is set to no.
You can modify it, there’s a lot that can be done with Unbound. For an initial set up we are using a generic setup.

pi-hole.conf clean no IPv6

server:
verbosity: 0
interface: 127.0.0.1
port: 5335
do-ip4: yes
do-udp: yes
do-tcp: yes
do-ip6: no
prefer-ip6: no
harden-glue: yes
harden-dnssec-stripped: yes
use-caps-for-id: no
edns-buffer-size: 1232
prefetch: yes
num-threads: 1
# Ensure privacy of local IP ranges
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

cd /etc/unbound/unbound.conf.d && nano pi-hole.conf

After it's saved, restart the Unbound service.

service unbound restart

Check for success

First, by systemctl status unbound

Then, you test that Unbound is running properly by running the three commands below (see the Unbound documentation).

Test 1: dig pi-hole.net @127.0.0.1 -p 5335 should return an IP address

Test 2: Run this twice: dig fail01.dnssec.works @127.0.0.1 -p 5335 you should return SERVFAIL at the second time

Test 3: dig dnssec.works @127.0.0.1 -p 5335 should return NOERROR.

Configure Pi-hole to use Unbound

If you didn't already do it during the installation, do it now:

  • In the GUI, go to SettingsDNS, uncheck all Upstream DNS Servers and add a Custom DNS server. In the entry field enter 127.0.0.1#5335
💡
This could be all you need.
But, we will go a step further, we will make it HA.

Making the DNS Highly Available

Create the other nodes

You should now create the two other Pi-hole servers, before you continue.
You will need their IP addresses and passwords later. They should have Pi-hole and Unbound as on the main one.

Keep Nodes in sync – Nebula-sync

Check their GitHub for the latest releases and download it

wget https://github.com/lovelaze/nebula-sync/releases/download/v0.3.0/nebula-sync_0.3.0_linux_amd64.tar.gz

Unpack

tar -xvzf nebula-sync_0.3.0_linux_amd64.tar.gz

Create the .env file.

Use CRON=* * * * * for testing, and CRON=0 * * * * for production.
Updating every minute is a bit excessive, hourly may be a better choice.

PRIMARY='http://ph1.example.com|password'
REPLICAS='http://ph2.example.com|password,http://ph3.example.com|password'
FULL_SYNC=true
CRON=* * * * *

CRON= 0**** is once an hour (@hourly in the non-standard way), ***** is once a minute.

Start nebula-sync

nebula-sync run --env-file .env

Run as a daemon

First, copy the executable to /usr/bin by cp nebula-sync /usr/bin then, you need to create a service file for systemd to be manage the service.

Create the service file

nano /etc/systemd/system/nebula-sync.service

Edit with sudo or run as root su -

[Unit]
Description=Nebula Sync Daemon

[Service]
ExecStart=/usr/bin/nebula-sync run --env-file /home/<user>/.env
WorkingDirectory=/home/<user>
Restart=always

[Install]
WantedBy=multi-user.target

Reload the systemd daemon to recognize the new service

sudo systemctl daemon-reload

Start your service

sudo systemctl start nebula-sync

Check for success

systemctl status nebula.service

Enable it to start on boot

sudo systemctl enable nebula-sync

High Availability with keepalived

Keepalived is designed to run on two separate hosts but share a virtual IP address. This ensures that if one goes down (the master), the backup will take over using the same virtual IP. In this example, the virtual IP is used as our backup DNS server.

Setup keepalived

Install Keepalived on both backup instances of Pi-hole (pi-2 and pi-3). Here we would like to have High Availability.

apt install keepalived

Get the interface name

ip a

Look for something like eth0@if36: ... the eth0 is what we need

Configure keepalived

Create and edit the file /etc/keepalived/keepalived.conf

Paste this information into the configuration file of the master and modify it as needed.

Create a password

Use any way you like, below 2 examples

< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo;
openssl rand -base64 32

Copy and edit the keepalived.conf files on pi-2 and pi-3

cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.orig && nano /etc/keepalived/keepalived.conf

The Keepalived Master – pi-2

Paste this into the keepalived.conf file, modify it with your password and IPs.

vrrp_instance VI_1 {
  state MASTER
  interface eth0
  virtual_router_id 54
  advert_int 1
  unicast_src_ip 10.10.1.55 
  unicast_peer {
    10.10.1.56
  }
  priority 20
  authentication {
    auth_type PASS
    auth_pass C35RUyBq
  }
  virtual_ipaddress {
    10.10.1.54/24    
  }
}

The Keepalived Backup – pi-3

Paste this into the keepalived.conf file, modify it with your password and IPs.

vrrp_instance VI_1 {
  state BACKUP
  interface eth0
  virtual_router_id 54
  advert_int 1
  unicast_src_ip 10.10.1.56
  unicast_peer {
    10.10.1.55  }
  priority 10
  authentication {
    auth_type PASS
    auth_pass C35RUyBq
  }
  virtual_ipaddress {
    10.10.1.54/24
  }
}

Enable the Keepalived service

Do this on both pi-2 and pi-3 on both instances

systemctl enable keepalived.service

Reboot

reboot now

Check for success

systemctl status keepalived.service

Test 1: Run a continuous ping to IP 192.0.2.54 and it should not break if you stop pi-2 or pi-3

Test 2: shutdown pi-2 and on pi-3 issue systemctl status keepalived.service and you should see a Entering MASTER STATE message

If you restart pi-2 you will see a Entering BACKUP STATE message.


Final tasks

Use the new DNS on your devices

Set up accordingly, use DHCP, set up on the device or any way you have to do it.

Remember, if you are using VLANs, to make them be able to access this setup or create their own setups, that is a bit extreme but sometimes needed.

Options

For things like DNS, we want them to be up and running after a boot. Go to the CT → Options and set Start at boot: ☑️. Select the boot order according to your needs.

Backup

Remember to set up Proxmox Backup Servers to keep backups.

Datacenter → Backup → hit Add to create a Backup Job

Backup Retention of snapshots

The retention of old backups depends on how often data changes and how important an older state may be in a specific workload.

For this example, we assume that you are doing daily backups, have a retention period of 10 years, and the period between backups stored gradually grows.

  • keep-last: 3 - even if only daily backups, an admin may want to create an extra one just before or after a big upgrade. Setting keep-last ensures this.
  • keep-hourly: not set - for daily backups this is not relevant. You cover extra manual backups already, with keep-last.
  • keep-daily: 13 - together with keep-last, which covers at least one day, this ensures that you have at least two weeks of backups.
  • keep-weekly: 8 - ensures that you have at least two full months of weekly backups.
  • keep-monthly: 11 - together with the previous keep settings, this ensures that you have at least a year of monthly backups.
  • keep-yearly: 9 - this is for the long term archive. As you covered the current year with the previous options, you would set this to nine for the remaining ones, giving you a total of at least 10 years of coverage.

Set Note Template to {{cluster}}, {{guestname}}, {{node}}, {{vmid}}

⚠️
High Availability is not a substitute for Backups!

Update root certificates with Crontab

ℹ️
Recommended updating the root DNS records every 2–3 or 4–6 months.

To edit the crontab, you type crontab -e and create this script and condition or edit the /etc/crontab file for running as root.

E.g. Run the script, update_root.sh, every 4 months on the 1st morning at 4:00 and report possible errors into the cron.log.

Create the script

Go to your stack and the folder for Pi-hole

ℹ️
Remember to edit the script and command with your data.

Execute this command:

touch update_root.sh && echo 'wget https://www.internic.net/domain/named.root -O /home/USER-ID/docker-stack/pi/unbound/root.hints' > update_root.sh 

Make the script executable

chmod +x update_root.sh

Edit the CRONTAB script

EDIT USER-ID and location of files. Command crontab -e and add your line

0 4 1 */4 * USER-ID /home/USER-ID/docker-stack/pi/root_update.sh >> /home/USER-ID/docker-stack/pi/cron.log 


References

Pi-hole [1] Unbound [2] Nebula-sync [3] Keepalived [4] CRON [5]
My old post [6]


  1. Pi-hole homepage, GitHub ↩︎

  2. Unbound is a validating, recursive, caching DNS resolver. It is designed to be fast and lean and incorporates modern features based on open standards. homepage, GitHub, DockerHub ↩︎

  3. Nebula-sync GitHub ↩︎

  4. Keepalived is a routing software written in C. The main goal of this project is to provide simple and robust facilities for loadbalancing and high-availability to Linux system and Linux based infrastructures.
    Homepage, GitHub ↩︎

  5. CRON - a daemon to execute scheduled commands man page, a quick and simple editor for cron schedule expressions by Cronitor
    Crontab guru ↩︎

  6. A redundant DNS - Pi-Stack older post ↩︎