Over-engineered ad-blocking with Pi-hole & Keepalived

20 November 2025

We'll be using Pi-hole installed in an Alpine container on Proxmox, but this should work on physical hosts with other base operating systems in using other hypervisors.

Firstly, we'll want to create our containers. As previously mentioned, we'll be using a Proxmox container and Alpine Linux v3.22. Because Pi-hole is very lightweight, we'll configure it with 2 CPUs, 128MB RAM, 256MB swap, and a 1GB HDD. Don't hesitate to assign more resources if required.

Pi-hole

Pi-hole is a caching DNS server used for network-wide advert blocking.

Install Pi-hole

Before we can install Pi-hole, we'll need to update our server and install Bash on all our hosts:

doas apk update
doas apk upgrade
doas apk add bash

Now we can download and run the Pi-hole installer script on all our hosts:

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

Configure Pi-hole

On all our hosts, we'll need to configure Pi-hole to bind to eth0 and listen on all addresses. This can be done from the web UI.

If you didn't take note of the temporary password during installation, you can set a new password with the following command:

doas pihole setpassword [password]

Entering no password will disable the password requirement (not recommended).

Keepalived

Keepalived is a service that creates virtual (or "floating") IPs that we give out to our clients.

Install Keepalived

Installing Keepalived on Alpine Linux is pretty straightforward. On all the hosts, we will run:

doas apk add keepalived

Configure Keepalived

By default Alpine does not create the folder or configuration file, so we will need to create them ourselves. We will run the following on all out hosts:

doas mkdir /etc/keepalived
doas touch /etc/keepalived/keepalived.conf

pihole-1

On your pihole-1 host, in the /etc/keepalived/keepalived.conf file, add the following:

vrrp_sync_group VG1 {
    VI_1
    VI_2
}
vrrp_sync_group VG2 {
    VI_3
    VI_4
}
vrrp_script pihole-FTL {
    script "killall -0 pihole-FTL"
    interval 1
}
vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 150
    advert_int 1
    authentication {
        auth_type AH
        auth_pass p@ssw0rd1!
    }
    virtual_ipaddress {
        192.0.2.41
    }
    track_script {
        pihole-FTL
    }
}
vrrp_instance VI_2 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 150
    advert_int 1
    authentication {
        auth_type AH
        auth_pass p@ssw0rd12
    }
    virtual_ipaddress {
        2001:db8::41
    }
    track_script {
        pihole-FTL
    }
}
vrrp_instance VI_3 {
    state BACKUP
    interface eth0
    virtual_router_id 52
    priority 50
    advert_int 1
    authentication {
        auth_type AH
        auth_pass p@ssw0rd3!
    }
    virtual_ipaddress {
        192.0.2.42
    }
    track_script {
        pihole-FTL
    }
}
vrrp_instance VI_4 {
    state BACKUP
    interface eth0
    virtual_router_id 52
    priority 50
    advert_int 1
    authentication {
        auth_type AH
        auth_pass p@ssw0rd4!
    }
    virtual_ipaddress {
        2001:db8::42
    }
    track_script {
        pihole-FTL
    }
}

pihole-2

On your pihole-2 host, in the /etc/keepalived/keepalived.conf file, add the following:

vrrp_sync_group VG1 {
    VI_1
    VI_2
}
vrrp_sync_group VG2 {
    VI_3
    VI_4
}
vrrp_script pihole-FTL {
    script "killall -0 pihole-FTL"
    interval 1
}
vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 50
    advert_int 1
    authentication {
        auth_type AH
        auth_pass p@ssw0rd1!
    }
    virtual_ipaddress {
        192.0.2.41
    }
    track_script {
        pihole-FTL
    }
}
vrrp_instance VI_2 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 50
    advert_int 1
    authentication {
        auth_type AH
        auth_pass p@ssw0rd12
    }
    virtual_ipaddress {
        2001:db8::41
    }
    track_script {
        pihole-FTL
    }
}
vrrp_instance VI_3 {
    state MASTER
    interface eth0
    virtual_router_id 52
    priority 150
    advert_int 1
    authentication {
        auth_type AH
        auth_pass p@ssw0rd3!
    }
    virtual_ipaddress {
        192.0.2.42
    }
    track_script {
        pihole-FTL
    }
}
vrrp_instance VI_4 {
    state MASTER
    interface eth0
    virtual_router_id 52
    priority 150
    advert_int 1
    authentication {
        auth_type AH
        auth_pass p@ssw0rd4!
    }
    virtual_ipaddress {
        2001:db8::42
    }
    track_script {
        pihole-FTL
    }
}

pihole-3

On your pihole-3 host, in the /etc/keepalived/keepalived.conf file, add the following:

vrrp_sync_group VG1 {
    VI_1
    VI_2
}
vrrp_sync_group VG2 {
    VI_3
    VI_4
}
vrrp_script pihole-FTL {
    script "killall -0 pihole-FTL"
    interval 1
}
vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type AH
        auth_pass p@ssw0rd1!
    }
    virtual_ipaddress {
        192.0.2.41
    }
    track_script {
        pihole-FTL
    }
}
vrrp_instance VI_2 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type AH
        auth_pass p@ssw0rd12
    }
    virtual_ipaddress {
        2001:db8::41
    }
    track_script {
        pihole-FTL
    }
}
vrrp_instance VI_3 {
    state BACKUP
    interface eth0
    virtual_router_id 52
    priority 100
    advert_int 1
    authentication {
        auth_type AH
        auth_pass p@ssw0rd3!
    }
    virtual_ipaddress {
        192.0.2.42
    }
    track_script {
        pihole-FTL
    }
}
vrrp_instance VI_4 {
    state BACKUP
    interface eth0
    virtual_router_id 52
    priority 100
    advert_int 1
    authentication {
        auth_type AH
        auth_pass p@ssw0rd4!
    }
    virtual_ipaddress {
        2001:db8::42
    }
    track_script {
        pihole-FTL
    }
}

This will configure 4 virtual IPs, 2x IP4 and 2 IPv6, and group them so they fail together. They are configured so that no host will have both IP groups so long as at least 2 nodes are up. Additionally it ensures the pihole-FTL service is running.

NOTE: The IPs used above are special ranges meant for documentation. They should be adjusted for your environment.

Run Keepalived on startup

To run the keepalived service at startup, we'll run the following on all 3 hosts:

doas rc-update add keepalived default