Set Up Keepalived on VPS for High Availability
Keepalived is a software solution that uses the VRRP (Virtual Router Redundancy Protocol) to create a Virtual IP Address (VIP) that floats between two or more servers. When the primary server (MASTER) fails, Keepalived automatically transfers the VIP to the standby server (BACKUP) within 1–3 seconds, ensuring your service remains online even when a server goes down.
This architecture is called Active-Passive HA — widely used with Nginx, HAProxy, MySQL, and web applications where zero downtime is a hard requirement.
Architecture Overview
Key components to understand before installation:
- MASTER node — the primary server that holds the Virtual IP under normal operation (higher priority)
- BACKUP node — the standby server that monitors MASTER and takes over the VIP when MASTER fails
- Virtual IP (VIP) — the IP that clients connect to; it fails over automatically
- VRRP Advertisement — heartbeat packets sent every 1 second between nodes
- Authentication — PASS or AH to prevent rogue nodes from stealing the VIP
Prerequisites
- 2 VPS instances (Ubuntu 22.04/24.04 or Debian 12) on the same subnet
- MASTER IP:
192.168.1.10, BACKUP IP:192.168.1.11 - Desired Virtual IP:
192.168.1.100(must be free in the subnet) - The same service (e.g., Nginx) running on both servers
- Root/sudo access on both machines
Install Keepalived on Both Servers
Run this on both MASTER and BACKUP:
# Ubuntu/Debian
sudo apt update
sudo apt install keepalived -y
# Check version
keepalived --version
# Enable auto-start on boot
sudo systemctl enable keepalived
Enable IP Forwarding and Non-Local Bind
Keepalived needs these kernel options to bind the Virtual IP even when it is not natively assigned to the interface:
# On BOTH servers
sudo tee /etc/sysctl.d/99-keepalived.conf <<EOF
net.ipv4.ip_nonlocal_bind = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
# Verify
sysctl net.ipv4.ip_nonlocal_bind
# Expected: net.ipv4.ip_nonlocal_bind = 1
ip_nonlocal_bind=1 is critical if you run Nginx/HAProxy on the VIP — without it, the service fails to start when the VIP is not yet assigned to this node.Configure Keepalived on MASTER (192.168.1.10)
Create the config file on the MASTER node:
sudo nano /etc/keepalived/keepalived.conf
global_defs {
notification_email {
[email protected]
}
notification_email_from [email protected]
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id MASTER_NODE
}
# Health check script for Nginx
vrrp_script chk_nginx {
script "/usr/bin/pgrep nginx"
interval 2 # Check every 2 seconds
weight -20 # Drop priority by 20 on failure
fall 2 # 2 consecutive failures = down
rise 2 # 2 consecutive successes = up
}
vrrp_instance VI_1 {
state MASTER
interface eth0 # Change to your actual interface (ip a)
virtual_router_id 51 # Must match on both servers (1-255)
priority 110 # MASTER must be higher than BACKUP
advert_int 1 # Heartbeat interval (seconds)
authentication {
auth_type PASS
auth_pass MyS3cr3t! # Use the same password on both servers
}
virtual_ipaddress {
192.168.1.100/24 # Virtual IP
}
track_script {
chk_nginx
}
}
Configure Keepalived on BACKUP (192.168.1.11)
The BACKUP config is similar but with different state and priority:
global_defs {
router_id BACKUP_NODE
}
vrrp_script chk_nginx {
script "/usr/bin/pgrep 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 # Lower than MASTER (110)
advert_int 1
authentication {
auth_type PASS
auth_pass MyS3cr3t! # Exactly the same as MASTER
}
virtual_ipaddress {
192.168.1.100/24
}
track_script {
chk_nginx
}
}
Start Keepalived
Start the service on both servers (MASTER first):
# On MASTER first
sudo systemctl start keepalived
sudo systemctl status keepalived
# Then on BACKUP
sudo systemctl start keepalived
sudo systemctl status keepalived
Test Failover
This is the most important step — always test before going live:
# From a client or third machine, verify the VIP is reachable
ping -c 3 192.168.1.100
curl http://192.168.1.100
# Check which server currently holds the VIP
# On MASTER:
ip addr show eth0 | grep 192.168.1.100
# Should show: inet 192.168.1.100/24
# Simulate failure: stop keepalived on MASTER
sudo systemctl stop keepalived
# Within 2-3 seconds, VIP should move to BACKUP
# On BACKUP:
ip addr show eth0 | grep 192.168.1.100
# Should show: inet 192.168.1.100/24
# Confirm service still responds
curl http://192.168.1.100
# Restore MASTER — VIP preempts back automatically
sudo systemctl start keepalived
nopreempt to the MASTER's vrrp_instance block. Use this when stability matters more than strict active/passive behavior.Custom HTTP Health Check Script
Instead of pgrep, write a script that tests an actual HTTP response:
sudo tee /etc/keepalived/check_nginx.sh <<'EOF'
#!/bin/bash
response=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/)
if [ "$response" = "200" ]; then
exit 0
else
exit 1
fi
EOF
sudo chmod +x /etc/keepalived/check_nginx.sh
Update the vrrp_script in your config:
vrrp_script chk_nginx {
script "/etc/keepalived/check_nginx.sh"
interval 3
weight -30
fall 2
rise 2
timeout 5
}
Notify Script (Alert on Failover)
Keepalived can call a script whenever the state changes:
sudo tee /etc/keepalived/notify.sh <<'EOF'
#!/bin/bash
TYPE=$1
NAME=$2
STATE=$3
case $STATE in
"MASTER")
echo "[$(date)] MASTER: $NAME became MASTER" >> /var/log/keepalived-notify.log
;;
"BACKUP")
echo "[$(date)] BACKUP: $NAME became BACKUP" >> /var/log/keepalived-notify.log
;;
"FAULT")
echo "[$(date)] FAULT: $NAME entered FAULT state" >> /var/log/keepalived-notify.log
;;
esac
EOF
sudo chmod +x /etc/keepalived/notify.sh
Add to vrrp_instance:
notify /etc/keepalived/notify.sh
Verify Keepalived is Working Correctly
# 1. Check service status
sudo systemctl status keepalived
# 2. Watch logs in real time
sudo journalctl -u keepalived -f
# 3. Confirm VIP is assigned to MASTER
ip addr show | grep "192.168.1.100"
# 4. Check current VRRP state
sudo journalctl -u keepalived --since "5 minutes ago" | grep -E "MASTER|BACKUP|FAULT"
# 5. View notify log
cat /var/log/keepalived-notify.log
tcpdump -i eth0 vrrp to capture VRRP advertisement packets and verify heartbeats are reaching both nodes — invaluable when debugging a failover that isn't triggering.Troubleshooting Common Issues
Error: "VRRP_Instance(VI_1) Transition to MASTER STATE"
The BACKUP node promoted itself to MASTER because it stopped receiving advertisements from the real MASTER. This usually means the firewall is blocking VRRP traffic (protocol 112).
# Allow VRRP protocol on both servers
sudo ufw allow proto 112 from 192.168.1.0/24
# Or with iptables:
sudo iptables -A INPUT -p 112 -j ACCEPT
sudo iptables -A OUTPUT -p 112 -j ACCEPT
# Confirm VRRP packets are passing
sudo tcpdump -i eth0 vrrp -n
Error: "ip address associated with VRID not present in MASTER advert"
The virtual_router_id or virtual_ipaddress does not match between the two nodes.
# Check that virtual_router_id is identical on both servers
grep virtual_router_id /etc/keepalived/keepalived.conf
# Fix any mismatch, then restart:
sudo systemctl restart keepalived
Error: "VRRP_Script(chk_nginx) timed out"
The health check script is slower than the configured timeout, causing Keepalived to treat the service as failed.
vrrp_script chk_nginx {
script "/etc/keepalived/check_nginx.sh"
interval 3
timeout 10 # Increase from default 5 to 10
weight -20
fall 3
rise 2
}
sudo systemctl restart keepalived
Error: "SECURITY VIOLATION - same VRID" in logs
Another Keepalived instance in the same subnet is using the same virtual_router_id. Common on cloud/shared environments.
# Check for competing VRRP traffic on the subnet
sudo tcpdump -i eth0 vrrp -n | grep "vrid"
# Change virtual_router_id to a unique value on both servers
# e.g. from 51 to 73, then restart both
VIP does not failover even when MASTER service crashes
The health check script is returning exit code 0 even when Nginx is stopped. Test the script manually.
# Test manually
sudo /etc/keepalived/check_nginx.sh
echo "Exit code: $?"
# If Nginx is stopped but you get 0, the script has a bug
# Stop Nginx and watch for priority change:
sudo systemctl stop nginx
sudo journalctl -u keepalived -f
# Should see priority drop and transition to BACKUP
Frequently Asked Questions
Does Keepalived work on all VPS cloud providers?
Not all providers support VRRP/Gratuitous ARP that Keepalived requires. Some providers like AWS EC2 and GCP block multicast/VRRP traffic by default — those require alternative approaches such as API-driven route table failover. Standard VPS instances from providers that offer private networking within the same datacenter generally work well.
What is the difference between Keepalived and HAProxy?
Keepalived handles IP failover at the network layer (Virtual IP), while HAProxy handles load balancing at the application layer. They complement each other perfectly — use Keepalived to create a floating VIP and have HAProxy bind to that VIP for both HA and load balancing in a single setup.
When MASTER comes back online, will the VIP return automatically?
By default, Keepalived uses preemption — the MASTER reclaims the VIP immediately when its priority exceeds the BACKUP's. This can cause a brief second disruption. To keep the VIP on the BACKUP until a manual switch, add nopreempt to the MASTER's vrrp_instance config.