Table of Contents
Pi-hole High Availability (HA) Architecture
This page documents the complete high-availability DNS filtering solution used in the TorresVault network. The setup provides fault-tolerant DNS, automatic sync, and a floating VIP so clients always reach a working Pi-hole instance.
Overview
Two Pi-hole servers provide DNS filtering:
- PiHole1 β 192.168.1.2
- PiHole2 β 192.168.1.4
- VIP (Virtual IP) β 192.168.1.5 (clients use this)
High availability is provided by:
- Keepalived for virtual IP failover
- Orbital-Sync (Docker) for Pi-hole configuration synchronization
- Unifi DHCP providing the VIP (192.168.1.5) as the primary DNS server
- Both Pi-holes fully running at all times, but only one holds the VIP
This ensures:
- Zero downtime if a Pi-hole reboots
- Identical configs on both systems
- DNS continuity for all VLANs
Network Layout
| Component | Hostname | IP Address | Role |
|---|---|---|---|
| Pi-hole 1 | pihole | 192.168.1.2 | Primary Pi-hole, may own VIP |
| Pi-hole 2 | pihole2 | 192.168.1.4 | Secondary Pi-hole, may own VIP |
| DNS VIP | n/a | 192.168.1.5 | Floating IP assigned via Keepalived |
| DHCP Server | UCG Max | 192.168.1.1 | Hands out DNS = 192.168.1.5 |
Keepalived Configuration
Both Pi-holes run `keepalived` and use VRRP.
Pi-hole1 (/etc/keepalived/keepalived.conf)
vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 200 advert_int 1 authentication { auth_type PASS auth_pass torresvault } virtual_ipaddress { 192.168.1.5/24 } }
Pi-hole2 (/etc/keepalived/keepalived.conf)
vrrp_instance VI_1 { state BACKUP interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass torresvault } virtual_ipaddress { 192.168.1.5/24 } }
Keepalived automatically transfers 192.168.1.5 to the healthy node.
Orbital-Sync (Pi-hole Sync)
Orbital-Sync keeps:
- Adlists
- Whitelists
- Blacklists
- Regex filters
- DHCP settings
- Groups
- Clients
β¦identical on both Pi-holes.
Docker Compose
Located at `/home/nathan/orbital-sync/docker-compose.yml`
version: β3β services: orbital-sync: image: mattwebbio/orbital-sync:latest container_name: orbital-sync volumes: - ./config.yml:/config.yml:ro restart: unless-stopped
Config File (config.yml)
primaryHost: baseUrl: http://192.168.1.2
secondaryHosts:
baseUrl: http://192.168.1.4
sync: intervalMinutes: 15 adlists: true whitelist: true blacklist: true regexWhitelist: true regexBlacklist: true groups: true clients: true localDns: true cname: true
Sync Interval
Orbital-sync runs automatically:
- Every 15 minutes
- Sync direction: 192.168.1.2 β 192.168.1.4
It detects changes on either Pi-hole and ensures both match.
Manual Sync Command
docker exec orbital-sync npm run sync
OR restart the container:
docker restart orbital-sync
Failover Behavior
Example Scenario
- Pi-hole1 goes offline
- Keepalived detects failure
- Pi-hole2 takes VIP 192.168.1.5
- Clients never notice β all DNS continues normally
- When Pi-hole1 recovers, it becomes BACKUP
Failover time: typically 1β2 seconds.
How to Test HA
1. Open a terminal on Pi-hole1 2. Run:
sudo systemctl stop keepalived
3. The VIP should instantly move:
- Pi-hole2 now shows: `hostname: PiHole2`
- And `ip addr` confirms `192.168.1.5`
4. Restart keepalived:
sudo systemctl start keepalived
Troubleshooting
Common issues:
- VIP not moving β check keepalived config
- Orbital-sync errors β config.yml path or permissions
- Docker needing `sudo` β add user to docker group
- Regex rules not syncing β ensure regex sync is enabled
- Pi-hole showing uneven stats β normal; traffic is not load-balanced, only HA
