How to Set Up Keepalived and VRRP for High-Availability Load Balancer Failover
Running a single HAProxy or Nginx load balancer is a single point of failure. If that one node crashes, every service behind it goes down. This guide walks through a complete Keepalived VRRP HAProxy failover setup.
By the end, you’ll have two servers sharing one virtual IP (VIP), automatic failover to the backup when the master goes down, and health checks that watch whether HAProxy itself is actually working, not just whether the server is on. If you haven’t set up HAProxy itself yet, check out our guide on configuring HAProxy as a high-performance reverse proxy first, then come back here to add redundancy.
Table of Contents
What Keepalived VRRP HAProxy Failover Actually Solves
VRRP (Virtual Router Redundancy Protocol) lets two or more routers share one virtual IP address. Only one node, the MASTER, holds the VIP and answers traffic at any given time. The other node, the BACKUP, listens for VRRP advertisements over multicast or unicast and silently waits.
Keepalived is the Linux service that runs VRRP and handles this election process. If the MASTER stops sending signals because it crashed, lost network, or HAProxy crashed, the BACKUP takes over the VIP in one to three seconds. Clients keep hitting the same IP and never notice the switch.
This setup doesn’t split traffic between the two nodes; only one is ever active at a time. It just removes the load balancer itself as a single point of failure, while HAProxy still handles balancing traffic across your app servers as usual.
Architecture and Prerequisites
For this tutorial, we’ll use a two-node setup:
- lb01 (10.0.0.11): Primary/MASTER node, HAProxy with Keepalived.
- lb02 (10.0.0.12): Secondary/BACKUP node, HAProxy with Keepalived.
- VIP: 10.0.0.10, the floating address clients and DNS actually point to.
- Both nodes on the same Layer 2 network segment. VRRP requires this, since failover relies on ARP.
- Ubuntu 22.04 or 24.04.
- Root or sudo access on both nodes.
- If you’re deploying on a cloud VPS or dedicated hardware, make sure the provider allows a secondary/floating IP or supports unicast VRRP.
A reliable Keepalived VRRP HAProxy failover deployment depends heavily on network latency between nodes, which is why running both in the same data center or region matters. Setting up two dedicated servers or VPS instances in the same region keeps VRRP latency low and failover detection fast.
Step 1. Install HAProxy on Both Nodes
Run this on both lb01 and lb02 nodes because both nodes need the same HAProxy configuration:
sudo apt update
sudo apt install haproxy -y
haproxy -v
Both nodes need the /etc/haproxy/haproxy.cfg so either one can handle traffic on its own. To create the file, open it with your desired text editor on each node:
sudo nano /etc/haproxy/haproxy.cfg
Add this minimal example:
global
log /dev/log local0
maxconn 4096
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
frontend fe_main
bind 10.0.0.10:80
default_backend be_web
backend be_web
balance roundrobin
server web1 10.0.0.21:80 check
server web2 10.0.0.22:80 check
Notice HAProxy binds to the VIP (10.0.0.10), not the server’s own IP. The BACKUP node doesn’t have that VIP yet, so HAProxy needs permission to bind to an address it doesn’t own yet:
echo "net.ipv4.ip_nonlocal_bind = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Without this sysctl setting, HAProxy will fail to start on the BACKUP node, and your Keepalived VRRP HAProxy failover will silently break the moment a failover occurs.
Enable HAProxy at boot but don’t start it manually yet if you want Keepalived’s notify scripts to control it. For now, enable and start it normally:
sudo systemctl enable haproxy
sudo systemctl start haproxy
sudo systemctl status haproxy
Step 2. Install Keepalived on Both Nodes
On both nodes, run the commands below to install Keepalived:
sudo apt update
sudo apt install keepalived -y
keepalived --version
Enable IP forwarding and non-local binding, which every Keepalived VRRP HAProxy failover node requires system-wide:
sudo tee -a /etc/sysctl.conf <<EOF
net.ipv4.ip_forward = 1
net.ipv4.ip_nonlocal_bind = 1
EOF
sudo sysctl -p
Step 3. Write the Health-Check Script for Keepalived VRRP HAProxy Failover
Keepalived needs a way to know HAProxy is actually healthy, not just that the OS is up. Create this script on both nodes so the Keepalived VRRP HAProxy failover election reacts to real HAProxy health, not just server uptime:
sudo tee /etc/keepalived/check_haproxy.sh <<'EOF'
#!/bin/bash
if systemctl is-active --quiet haproxy; then
exit 0
else
exit 1
fi
EOF
sudo chmod +x /etc/keepalived/check_haproxy.sh
This just checks if the HAProxy process is running. For a stricter check that confirms HAProxy is actually accepting connections, replace it with a curl request to HAProxy’s stats page instead of just checking the systemd status.
Step 4. Configure Keepalived on the Master Node (lb01)
To configure Keepalived on the master node (lb01), create the following file:
sudo nano /etc/keepalived/keepalived.conf
Add this config:
global_defs {
router_id LB01
enable_script_security
script_user root
}
vrrp_script chk_haproxy {
script "/etc/keepalived/check_haproxy.sh"
interval 2
timeout 2
fall 2
rise 2
weight 20
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 150
advert_int 1
nopreempt
authentication {
auth_type PASS
auth_pass K3ep@liv
}
virtual_ipaddress {
10.0.0.10/24
}
track_script {
chk_haproxy
}
notify_master "/etc/keepalived/notify.sh MASTER"
notify_backup "/etc/keepalived/notify.sh BACKUP"
notify_fault "/etc/keepalived/notify.sh FAULT"
}
A few details matter here for a correct Keepalived VRRP HAProxy failover configuration:
- Set both nodes to
state BACKUP; priority decides who wins. This is required fornopreemptto work. virtual_router_idmust match on both nodes so they recognize each other.- lb01 has higher
priority, so it becomes MASTER first. auth_passcan’t exceed 8 characters; keepalived cuts off anything longer.nopreemptstops lb01 from grabbing the VIP back right after it recovers, avoiding a second failover. Remove it if you want lb01 to always take over again once it’s healthy.
Step 5. Configure Keepalived on the Backup Node (lb02)
Create the matching /etc/keepalived/keepalived.conf file on lb02:
global_defs {
router_id LB02
enable_script_security
script_user root
}
vrrp_script chk_haproxy {
script "/etc/keepalived/check_haproxy.sh"
interval 2
timeout 2
fall 2
rise 2
weight 20
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
nopreempt
authentication {
auth_type PASS
auth_pass K3ep@liv
}
virtual_ipaddress {
10.0.0.10/24
}
track_script {
chk_haproxy
}
notify_master "/etc/keepalived/notify.sh MASTER"
notify_backup "/etc/keepalived/notify.sh BACKUP"
notify_fault "/etc/keepalived/notify.sh FAULT"
}
The auth_pass and virtual_router_id must match between nodes or the two Keepalived processes will never recognize each other as peers in the same Keepalived VRRP HAProxy failover group.
Step 6. Add Notify Scripts for Logging and Alerts
At this point, you must create the Notify Scripts file on both nodes so you get a log entry every time a role transition happens.
Create the file and make the file executable:
sudo tee /etc/keepalived/notify.sh <<'EOF'
#!/bin/bash
STATE=$1
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "$DATE - Keepalived transitioned to $STATE" >> /var/log/keepalived-state.log
EOF
sudo chmod +x /etc/keepalived/notify.sh
Note: Swap the echo line for a curl call to Slack, PagerDuty, or your monitoring tool if you want real-time alerts whenever a failover happens.
Step 7. Handle Cloud and Firewall Restrictions
A Keepalived VRRP HAProxy failover deployment on public cloud usually breaks here:
Standard VRRP uses multicast (224.0.0.18) and the IP protocol number 112. Many cloud providers block multicast and non-standard protocols on their virtual networks, which silently breaks failover.
Two fixes for this, include:
1. Switch to unicast VRRP: Add these lines inside vrrp_instance on each node, pointing to the peer’s IP:
unicast_src_ip 10.0.0.11
unicast_peer {
10.0.0.12
}
On lb02, swap the IPs, unicast_src_ip becomes 10.0.0.12, and the peer becomes 10.0.0.11.
2. Open the firewall for VRRP if you’re not using unicast:
sudo ufw allow from 10.0.0.0/24 to any proto vrrp
sudo ufw allow from 224.0.0.18
On cloud VPS platforms, check whether you need to manually attach the floating IP to the active instance. Some providers, don’t support the standard ARP-based VIP failover at all; you’d need the notify script to call their API instead to move the IP. This is the most common reason a correctly Keepalived VRRP HAProxy failover setup still fails on public cloud.
Step 8. Start Keepalived and Verify the VIP
Start the service on both nodes:
sudo systemctl enable keepalived
sudo systemctl start keepalived
sudo systemctl status keepalived
On lb01, confirm the VIP is attached:
ip addr show eth0
You should see 10.0.0.10/24 listed as a secondary address on lb01, and absent from lb02’s interface.
Check the Keepalived logs to confirm the election:
sudo journalctl -u keepalived -f
You should see lb01 log Entering MASTER STATE and lb02 log Entering BACKUP STATE. This confirms the base Keepalived VRRP HAProxy failover election is working before you move on to failure testing.
Step 9. Test Failover
At this point, you can prove that your Keepalived VRRP HAProxy failover setup works.
Test 1. stop HAProxy on the master:
# On lb01
sudo systemctl stop haproxy
Within a few seconds, chk_haproxy should fail, drop lb01’s priority by the configured weight, and lb02 should take over the VIP.
Confirm with the command below on lb02:
ip addr show eth0
Check on both nodes:
journalctl -u keepalived -f
Test 2. simulate a full node outage:
# On lb01
sudo systemctl stop keepalived
lb02 should promote itself the moment it stops receiving VRRP advertisements from lb01.
Test 3. continuous connectivity check from a third machine:
while true; do curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" http://10.0.0.10/; sleep 1; done
Run this while doing Test 1 or Test 2. You should see at most one or two failed requests during the switch. This is the proof your Keepalived VRRP HAProxy failover actually works.
Then, restart HAProxy and Keepalived on lb01:
sudo systemctl start haproxy keepalived
Conform it rejoins as BACKUP without taking the VIP back, thanks to nopreempt.
Step 10. Harden and Monitor the Cluster
A few extra steps worth adding once your Keepalived VRRP HAProxy failover setup is working:
- Add
garp_master_delayandgarp_master_refreshtovrrp_instanceto keep re-announcing the VIP. This helps on switches that clear ARP cache quickly. - Send VRRP state changes to your logging tool through the notify script instead of just a text file.
- With more than two nodes, keep one
virtual_router_idper VIP group and usepriorityto rank the backups. - If HAProxy handles TLS, run separate failover pairs for HTTP and HTTPS, and keep certificates synced between lb01 and lb02.
- Re-run the failover tests from Step 9 after any OS or Keepalived update; outdated configs are the most common cause of failover breaking later.
Conclusion
A single load balancer is always one failure away from an outage. Pairing HAProxy with Keepalived and VRRP turns two separate servers into one self-healing pair that keeps the same virtual IP alive even if one node goes down. With health checks driving the failover, nopreempt preventing needless switches, and unicast VRRP handling cloud restrictions, you now have a setup that’s actually ready for production.
Just make sure both nodes sit on reliable and low-latency hosting in the same region, since VRRP’s failover speed depends on how fast the two servers can talk to each other.
We hope you enjoy this Keepalived VRRP HAProxy failover setup.
For more information about every Keepalived directive used above, check out the official Keepalived configuration manual.
FAQs
What is VRRP used for in a Keepalived HAProxy setup?
VRRP lets two servers share one virtual IP address, with only one node active at a time, so traffic keeps flowing if the active node fails.
Can Keepalived work with Nginx instead of HAProxy?
Yes. Swap the health-check script to monitor the Nginx process or systemd unit instead of HAProxy; the VRRP and Keepalived configuration stays the same.
Why do both nodes need “state BACKUP” if one should be master?
Setting both to BACKUP and using priority lets nopreempt work correctly, avoiding a double failover when the original master recovers.
Does Keepalived load-balance traffic between the two nodes?
No. Only one node holds the VIP and serves traffic at a time; load balancing across backend servers is HAProxy’s job, not Keepalived’s.