# PiHole - Add KeepAlive on two Pi's

Setting up `keepalived` for dual Pi-holes requires installing the package on both machines, writing a short configuration file on each, and pointing your network/AD DNS forwarder to the shared Virtual IP (VIP).

### Prerequisites &amp; Example Values

Check your network interface name using `ip a` (usually `eth0` for wired or `wlan0` for Wi-Fi).

- **Pi-hole A (Primary):** `192.168.1.10` (Interface: `eth0`)
- **Pi-hole B (Secondary):** `192.168.1.11` (Interface: `eth0`)
- **Shared Virtual IP (VIP):** `192.168.1.99` (Choose an unused static IP outside your DHCP range)

### Step 1: Install `keepalived` on Both Pi-holes

Run the following commands on **both** Pi-hole servers:

```
sudo apt update
sudo apt install -y keepalived

```

<div _ngcontent-ng-c1274178722="" class="code-block ng-tns-c1274178722-54 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjEn8mPmpmWAxUAAAAAHQAAAAAQew" decode-data-ved="1" id="bkmrk--1" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_1dd4eb690efcb467","c_bd2896b6cc8e6d51",null,"rc_9ff016f27158756a",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1274178722="" class="formatted-code-block-internal-container ng-tns-c1274178722-54"><div _ngcontent-ng-c1274178722="" class="animated-opacity ng-tns-c1274178722-54"></div></div></div>Allow the Linux kernel to bind to non-local IP addresses (needed so Pi-hole can bind to the VIP even when it's not the active master):

```
echo "net.ipv4.ip_nonlocal_bind=1" | sudo tee -a /etc/sysctl.d/99-keepalived.conf
sudo sysctl --system

```

<div _ngcontent-ng-c1274178722="" class="code-block ng-tns-c1274178722-55 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjEn8mPmpmWAxUAAAAAHQAAAAAQfA" decode-data-ved="1" id="bkmrk--4" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_1dd4eb690efcb467","c_bd2896b6cc8e6d51",null,"rc_9ff016f27158756a",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1274178722="" class="formatted-code-block-internal-container ng-tns-c1274178722-55"><div _ngcontent-ng-c1274178722="" class="animated-opacity ng-tns-c1274178722-55"></div></div></div>### Step 2: Configure Pi-hole A (Primary / MASTER)

Create the configuration file on **Pi-hole A**:

```
sudo nano /etc/keepalived/keepalived.conf

```

<div _ngcontent-ng-c1274178722="" class="code-block ng-tns-c1274178722-56 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjEn8mPmpmWAxUAAAAAHQAAAAAQfQ" decode-data-ved="1" id="bkmrk--7" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_1dd4eb690efcb467","c_bd2896b6cc8e6d51",null,"rc_9ff016f27158756a",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1274178722="" class="formatted-code-block-internal-container ng-tns-c1274178722-56"><div _ngcontent-ng-c1274178722="" class="animated-opacity ng-tns-c1274178722-56"></div></div></div>Paste the following configuration:

```
vrrp_script check_pihole {
    # Verify Pi-hole DNS service is answering queries locally
    script "dig +short +time=1 +tries=1 @127.0.0.1 google.com > /dev/null 2>&1"
    interval 2
    weight -20
}

vrrp_instance VI_PIHOLE {
    state MASTER
    interface eth0               # Replace with your interface if different (e.g., wlan0)
    virtual_router_id 51         # Must be identical on both Pis (1-255)
    priority 100                 # Higher priority = primary holder of VIP
    advert_int 1

    authentication {
        auth_type PASS
        auth_pass SecretPass123  # Must match on both Pis (max 8 chars)
    }

    virtual_ipaddress {
        192.168.1.99/24          # Your shared VIP
    }

    track_script {
        check_pihole
    }
}

```

<div _ngcontent-ng-c1274178722="" class="code-block ng-tns-c1274178722-57 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjEn8mPmpmWAxUAAAAAHQAAAAAQfg" decode-data-ved="1" id="bkmrk--10" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_1dd4eb690efcb467","c_bd2896b6cc8e6d51",null,"rc_9ff016f27158756a",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1274178722="" class="formatted-code-block-internal-container ng-tns-c1274178722-57"><div _ngcontent-ng-c1274178722="" class="animated-opacity ng-tns-c1274178722-57"></div></div></div>Save and exit (`Ctrl+O`, `Enter`, `Ctrl+X`).

### Step 3: Configure Pi-hole B (Secondary / BACKUP)

Create the configuration file on **Pi-hole B**:

```
sudo nano /etc/keepalived/keepalived.conf

```

<div _ngcontent-ng-c1274178722="" class="code-block ng-tns-c1274178722-58 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjEn8mPmpmWAxUAAAAAHQAAAAAQfw" decode-data-ved="1" id="bkmrk--13" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_1dd4eb690efcb467","c_bd2896b6cc8e6d51",null,"rc_9ff016f27158756a",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1274178722="" class="formatted-code-block-internal-container ng-tns-c1274178722-58"><div _ngcontent-ng-c1274178722="" class="animated-opacity ng-tns-c1274178722-58"></div></div></div>Paste the following configuration:

```
vrrp_script check_pihole {
    script "dig +short +time=1 +tries=1 @127.0.0.1 google.com > /dev/null 2>&1"
    interval 2
    weight -20
}

vrrp_instance VI_PIHOLE {
    state BACKUP
    interface eth0               # Replace with your interface if different
    virtual_router_id 51         # Must match Pi-hole A
    priority 90                  # Lower priority than Master
    advert_int 1

    authentication {
        auth_type PASS
        auth_pass SecretPass123  # Must match Pi-hole A
    }

    virtual_ipaddress {
        192.168.1.99/24          # Same shared VIP
    }

    track_script {
        check_pihole
    }
}

```

<div _ngcontent-ng-c1274178722="" class="code-block ng-tns-c1274178722-59 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjEn8mPmpmWAxUAAAAAHQAAAAAQgAE" decode-data-ved="1" id="bkmrk--16" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_1dd4eb690efcb467","c_bd2896b6cc8e6d51",null,"rc_9ff016f27158756a",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1274178722="" class="formatted-code-block-internal-container ng-tns-c1274178722-59"><div _ngcontent-ng-c1274178722="" class="animated-opacity ng-tns-c1274178722-59"></div></div></div>Save and exit (`Ctrl+O`, `Enter`, `Ctrl+X`).

### Step 4: Start and Enable the Services

Run these commands on **both** Pi-holes:

```
sudo systemctl enable keepalived
sudo systemctl restart keepalived

```

<div _ngcontent-ng-c1274178722="" class="code-block ng-tns-c1274178722-60 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjEn8mPmpmWAxUAAAAAHQAAAAAQgQE" decode-data-ved="1" id="bkmrk--19" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_1dd4eb690efcb467","c_bd2896b6cc8e6d51",null,"rc_9ff016f27158756a",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1274178722="" class="formatted-code-block-internal-container ng-tns-c1274178722-60"><div _ngcontent-ng-c1274178722="" class="animated-opacity ng-tns-c1274178722-60"></div></div></div>### Step 5: Verify Failover Operation

1. **Check which Pi holds the VIP:** On **Pi-hole A**, run:
    
    ```
    ip a show eth0
    
    ```
    
    <div _ngcontent-ng-c1274178722="" class="code-block ng-tns-c1274178722-61 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjEn8mPmpmWAxUAAAAAHQAAAAAQggE" decode-data-ved="1" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_1dd4eb690efcb467","c_bd2896b6cc8e6d51",null,"rc_9ff016f27158756a",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1274178722="" class="formatted-code-block-internal-container ng-tns-c1274178722-61"><div _ngcontent-ng-c1274178722="" class="animated-opacity ng-tns-c1274178722-61"></div></div></div>You should see `inet 192.168.1.99/24` listed as a secondary IP on the interface.
2. **Test Failover:**
    
    
    - Stop the service on Pi-hole A:
        
        ```
        sudo systemctl stop keepalived
        
        ```
        
        <div _ngcontent-ng-c1274178722="" class="code-block ng-tns-c1274178722-62 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjEn8mPmpmWAxUAAAAAHQAAAAAQgwE" decode-data-ved="1" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_1dd4eb690efcb467","c_bd2896b6cc8e6d51",null,"rc_9ff016f27158756a",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1274178722="" class="formatted-code-block-internal-container ng-tns-c1274178722-62"><div _ngcontent-ng-c1274178722="" class="animated-opacity ng-tns-c1274178722-62"></div></div></div>
    - Run `ip a show eth0` on **Pi-hole B** — `192.168.1.99` will immediately appear on Pi-hole B.
    - Start `keepalived` back up on Pi-hole A:
        
        ```
        sudo systemctl start keepalived
        
        ```
        
        <div _ngcontent-ng-c1274178722="" class="code-block ng-tns-c1274178722-63 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjEn8mPmpmWAxUAAAAAHQAAAAAQhAE" decode-data-ved="1" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_1dd4eb690efcb467","c_bd2896b6cc8e6d51",null,"rc_9ff016f27158756a",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1274178722="" class="formatted-code-block-internal-container ng-tns-c1274178722-63"><div _ngcontent-ng-c1274178722="" class="animated-opacity ng-tns-c1274178722-63"></div></div></div>
    - Pi-hole A will reclaim the VIP because its priority (`100`) is higher than Pi-hole B's (`90`).
3. **Update Upstream Settings:** Set the DNS forwarder address on your Active Directory DNS server, Synology NAS, or pfSense/DHCP scope to point strictly to the Virtual IP (**`192.168.1.99`**).