Set Up Keepalived High Availability on VPS with VRRP

September 16, 2026 · 12 min read · Linux, Networking, HA

Keepalived High Availability VRRP on Linux VPS

Keepalived is an open-source software that provides High Availability (HA) for Linux servers using the VRRP (Virtual Router Redundancy Protocol) standard. It creates a Virtual IP (VIP) that automatically migrates to a standby server within seconds when the primary server fails — keeping your services online with minimal downtime.

This guide walks you through configuring Keepalived in Master/Backup mode on two VPS nodes, with health checks for Nginx or HAProxy.

How Keepalived and VRRP Work

VRRP (RFC 3768/5798) allows a group of servers to share a single Virtual IP address. Only one server — the Master — holds the VIP and handles real traffic at any given time. The other server (Backup) listens for heartbeat packets. If the Master goes silent beyond the threshold, the Backup promotes itself to Master and takes ownership of the VIP.

RoleDescriptionPriority
MasterHolds the Virtual IP, handles all trafficHigher (e.g., 101)
BackupStandby — takes VIP only when Master failsLower (e.g., 100)

Prerequisites

You need at least two VPS nodes on the same network (same subnet or private VLAN):

Note: On cloud VPS providers (Vultr, DigitalOcean, Hetzner), you typically need to enable IP Floating or Allowed IPs on the network interface to allow assigning a Virtual IP. Check your provider's documentation.

Install Keepalived

Run on both servers (node1 and node2):

Ubuntu / Debian

sudo apt update
sudo apt install -y keepalived

CentOS / AlmaLinux / Rocky Linux

sudo dnf install -y keepalived

Enable IP Nonlocal Bind (Required)

Keepalived needs to bind to the Virtual IP address, which doesn't yet exist on the interface. Enable this kernel parameter:

echo "net.ipv4.ip_nonlocal_bind = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Configure Keepalived on Master (node1)

sudo nano /etc/keepalived/keepalived.conf
global_defs {
   router_id node1
   script_user root
   enable_script_security
}

vrrp_script chk_nginx {
    script "/usr/bin/systemctl is-active nginx"
    interval 2       # Check every 2 seconds
    weight -20       # Reduce priority by 20 when nginx fails
    fall 2           # 2 consecutive failures trigger failover
    rise 2           # 2 consecutive successes trigger recovery
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0           # Your network interface (check with: ip a)
    virtual_router_id 51     # Must match on both servers (1-255)
    priority 101             # Master must be higher than Backup
    advert_int 1             # Send heartbeat every 1 second

    authentication {
        auth_type PASS
        auth_pass MySecretPass2026   # Must match on both sides
    }

    virtual_ipaddress {
        192.168.1.100/24 dev eth0    # Virtual IP address
    }

    track_script {
        chk_nginx
    }
}

Configure Keepalived on Backup (node2)

sudo nano /etc/keepalived/keepalived.conf
global_defs {
   router_id node2
   script_user root
   enable_script_security
}

vrrp_script chk_nginx {
    script "/usr/bin/systemctl is-active nginx"
    interval 2
    weight -20
    fall 2
    rise 2
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51     # Must match Master
    priority 100             # Backup must be lower than Master
    advert_int 1

    authentication {
        auth_type PASS
        auth_pass MySecretPass2026   # Must match
    }

    virtual_ipaddress {
        192.168.1.100/24 dev eth0
    }

    track_script {
        chk_nginx
    }
}

Start and Enable Keepalived

Run on both servers:

sudo systemctl enable --now keepalived
sudo systemctl status keepalived

Configure Notify Script (Failover Alerts)

Create a script to log or alert when the VRRP state changes:

sudo nano /etc/keepalived/notify.sh
#!/bin/bash
TYPE=$1
NAME=$2
STATE=$3

case $STATE in
    "MASTER")
        echo "[$(date)] ${NAME} became MASTER" >> /var/log/keepalived-notify.log
        ;;
    "BACKUP")
        echo "[$(date)] ${NAME} became BACKUP" >> /var/log/keepalived-notify.log
        ;;
    "FAULT")
        echo "[$(date)] ${NAME} entered FAULT state" >> /var/log/keepalived-notify.log
        ;;
esac
sudo chmod +x /etc/keepalived/notify.sh

Add to the vrrp_instance block on both servers:

    notify /etc/keepalived/notify.sh

Health Check for HAProxy Instead of Nginx

If you're using HAProxy instead of Nginx, change the check script:

vrrp_script chk_haproxy {
    script "/usr/bin/systemctl is-active haproxy"
    interval 2
    weight -20
    fall 2
    rise 2
}

Or use a TCP port check for more flexibility:

vrrp_script chk_port {
    script "/bin/bash -c 'echo > /dev/tcp/127.0.0.1/80'"
    interval 3
    weight -20
    fall 3
    rise 2
    timeout 2
}

Configure Firewall for VRRP

VRRP uses IP Protocol 112 (multicast 224.0.0.18) — open the firewall on both nodes:

UFW

sudo ufw allow proto vrrp
# Or restrict to the specific peer IP
sudo ufw allow from 192.168.1.11 proto vrrp

iptables

sudo iptables -A INPUT -p vrrp -j ACCEPT
sudo iptables -A OUTPUT -p vrrp -j ACCEPT

firewalld (CentOS/AlmaLinux)

sudo firewall-cmd --add-protocol=vrrp --permanent
sudo firewall-cmd --reload

Verify Keepalived is Working Correctly

# Check if the Virtual IP is assigned to eth0 (should be on Master/node1)
ip addr show eth0 | grep 192.168.1.100

# Stream Keepalived logs
journalctl -u keepalived -f

# Check current state
sudo systemctl status keepalived

# Test failover — stop Keepalived on Master
sudo systemctl stop keepalived   # on node1
# Verify VIP moved to node2
ip addr show eth0 | grep 192.168.1.100  # on node2 (should appear)

# Ping the VIP continuously during the test
ping 192.168.1.100
Tip: Use tcpdump -i eth0 -p vrrp to capture VRRP heartbeat packets between node1 and node2. This is invaluable for debugging when the VIP doesn't migrate as expected.

Troubleshooting Common Issues

Error: "VRRP_Script(chk_nginx) timed_out"

The health check script is taking longer than the default timeout (5 seconds). Add an explicit timeout to the script block:

vrrp_script chk_nginx {
    script "/usr/bin/systemctl is-active nginx"
    interval 2
    weight -20
    timeout 5   # Add this line
    fall 2
    rise 2
}

Error: "ip address associated with VRID not present in MASTER advert"

The virtual_router_id values on the two servers don't match, or another Keepalived instance on the subnet is using the same VRID.

# Check the VRID on both nodes
sudo grep virtual_router_id /etc/keepalived/keepalived.conf
# Values must be identical, and unique within the subnet (1-255)

VIP is assigned but clients can't connect after failover

The client's ARP cache still has the old MAC address mapping. Force an ARP refresh:

# On the client or upstream router
arp -d 192.168.1.100

# On Linux, send a new ARP request
arping -c 3 192.168.1.100

Error: "Can't open PID file /run/keepalived.pid"

Permission issue writing to /run/. Override the service unit:

sudo systemctl edit keepalived --force
# Add:
[Service]
RuntimeDirectory=keepalived
RuntimeDirectoryMode=0755

sudo systemctl daemon-reload
sudo systemctl restart keepalived

VIP doesn't return to Master after it recovers (Preemption)

By default, Keepalived preempts (moves the VIP back to Master) when the Master recovers. To disable or delay this behavior:

# Add to vrrp_instance on the Master node
nopreempt   # VIP won't auto-return to Master

# Or add a delay before preemption
preempt_delay 30   # Wait 30 seconds before preempting

Frequently Asked Questions

What is the difference between Keepalived and HAProxy?

Keepalived handles IP-level failover — it migrates a Virtual IP from one server to another when a failure is detected. HAProxy handles application-level load balancing — it distributes requests across multiple backend servers. The two tools are commonly used together: Keepalived manages the VIP that clients connect to (ensuring HA for HAProxy itself), while HAProxy distributes traffic across your web servers.

How many Virtual IPs can one Keepalived instance manage?

Multiple VIPs per vrrp_instance are supported. Simply list them in the virtual_ipaddress {} block:

virtual_ipaddress {
    192.168.1.100/24
    192.168.1.101/24
    10.0.0.50/24
}

Can Keepalived work with public IPs on a cloud VPS?

With limitations. Most cloud providers block VRRP multicast on public networks for security reasons. The standard approach is to use the VIP on a private network, then point a cloud load balancer's public IP to the VIP. Alternatively, use the provider's Floating IP feature with an API-based failover script that re-assigns the floating IP when a node goes down.