Set Up Keepalived High Availability on VPS with VRRP
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.
| Role | Description | Priority |
|---|---|---|
| Master | Holds the Virtual IP, handles all traffic | Higher (e.g., 101) |
| Backup | Standby — takes VIP only when Master fails | Lower (e.g., 100) |
Prerequisites
You need at least two VPS nodes on the same network (same subnet or private VLAN):
- node1 (Master): IP
192.168.1.10 - node2 (Backup): IP
192.168.1.11 - Virtual IP:
192.168.1.100(clients connect to this address)
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
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.