How to Build a High-Availability Pi-hole Cluster (With Auto-Sync)

If you rely on Pi-hole for network-wide ad blocking, you know the pain of “The Internet is down!” the moment you reboot your Raspberry Pi for updates. The solution? Two Pi-holes are better than one.

In this guide, I’ll explain how to set up a Primary and Secondary Pi-hole and use a tool called gravity-sync to ensure that when you allow a domain on one, it’s automatically allowed on the other.

In the config below, I’ll assume that your router is configured as follows:
Router IP : 192.168.1.1
Default Gateway: 192.168.1.1
Subnet mask: 255.255.255.0
Router configured for DHCP with a range of 192.168.1.10-192.168.1.254
(This leaves a range of static addresses for me to use).

The Goal

  • Primary Pi: 192.168.1.3 (Your Source of Truth)
  • Secondary Pi: 192.168.1.4 (The Shadow)
  • The Result: Identical blocking lists and settings across both devices.

Step 1: Install Pi-hole on Both Devices

First, ensure both Raspberry Pis have a static IP address and a fresh install of Pi-hole.

Bash

curl -sSL https://install.pi-hole.net | bash

Follow the on-screen prompts. For the secondary Pi, you can use any upstream DNS (like Google or Cloudflare).


Step 2: Prepare the Secondary Pi (The Handshake)

For the Primary Pi to “talk” to the Secondary Pi without asking for a password every five minutes, we need to set up a secure handshake.

1. Create the SSH Key (On the PRIMARY)

Run this on your Primary Pi:

Bash

# Create a dedicated key for syncing
ssh-keygen -t rsa -b 4096 -f /etc/gravity-sync/gravity-sync.rsa -N ""

2. Copy the Key to the Secondary

Bash

ssh-copy-id -i /etc/gravity-sync/gravity-sync.rsa.pub admin@192.168.1.4

(Enter your password one last time. Now, the Primary can “log in” to the Secondary automatically.)

3. Grant “Sudo” Permissions (On the SECONDARY)

This is where many people get stuck. Pi-hole requires root privileges to reload. We need to allow the sync process to reload the DNS engine without a password. On the Secondary Pi, run:

Bash

sudo visudo

Scroll to the bottom and add this line:

Plaintext

admin ALL=(ALL) NOPASSWD: /usr/local/bin/pihole

Step 3: Install and Configure Gravity Sync

Now, we install the bridge that connects the two. We do this on the Primary Pi.

1. Install the Script

Bash

curl -sSL https://raw.githubusercontent.com/vmstan/gs-install/main/gs-install.sh | bash

2. Configure the Sync

The automated wizard can sometimes trip over version differences, so we will manually tune the configuration file. Open it on your Primary Pi:

Bash

sudo nano /etc/gravity-sync/gravity-sync.conf

Paste this exact configuration (replacing IPs as needed):

Plaintext

# REMOTE SETTINGS
REMOTE_HOST='192.168.1.4'
REMOTE_USER='admin'
SSH_PKI='/etc/gravity-sync/gravity-sync.rsa'

# THE RELOAD FIX (CRITICAL)
# These lines ensure the secondary actually loads the new data
REMOTE_FTL_RELOAD='sudo pihole reloaddns'
RELOAD_FTL='sudo pihole reloaddns'

# OPTIMISATION
SKIP_DNS_CHECK='1'

Step 4: The First Sync

On your Primary Pi, let’s run the first manual push to make sure everything is working:

Bash

gravity-sync push

Look for ✓ Pushing the local Gravity Database and ✓ Reloading remote FTLDNS services. If you see those, you are successful!


Step 5: Set it to Autopilot

Finally, tell Gravity Sync to run in the background. It will detect any changes you make to the Primary (like adding a new allowlist domain) and push them to the Secondary within minutes.

Bash

gravity-sync auto

How to use your new Setup

  1. Management: Always add your new “Allow” or “Deny” domains on the Primary Pi UI (192.168.1.3/admin).
  2. Router Config: Set your Router’s DNS settings to point to both IPs.
    • Primary DNS: 192.168.1.3
    • Secondary DNS: 192.168.1.4
  3. Failover: If you need to take the Primary Pi offline for an update, your network won’t even blink. The Secondary Pi is already running with an identical copy of your rules.

Leave a Reply

Your email address will not be published. Required fields are marked *