====== 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 ----