Set up keepalived with HAProxy to create a high availability load balancer cluster with automatic failover, backend health monitoring, and VRRP protocol for seamless traffic distribution across multiple servers.
Prerequisites
- Two or more servers for load balancer cluster
- Three or more backend servers
- Root or sudo access
- Basic networking knowledge
What this solves
High availability load balancing requires both load distribution and automatic failover when the primary load balancer fails. This tutorial shows you how to configure keepalived with HAProxy to create a resilient load balancer cluster that monitors backend server health and automatically switches between primary and backup load balancers using VRRP (Virtual Router Redundancy Protocol).
Step-by-step configuration
Update system packages
Start by updating your package manager on both load balancer servers to ensure you get the latest versions.
sudo apt update && sudo apt upgrade -yInstall HAProxy and keepalived
Install both HAProxy for load balancing and keepalived for VRRP failover on your primary and backup servers.
sudo apt install -y haproxy keepalivedConfigure HAProxy with backend health checks
Create the HAProxy configuration with backend servers and health monitoring. This configuration will be identical on both load balancer nodes.
global
daemon
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
log stdout local0
defaults
mode http
timeout connect 5000
timeout client 50000
timeout server 50000
option httplog
option dontlognull
option redispatch
retries 3
maxconn 2000
frontend web_frontend
bind *:80
bind *:443 ssl crt /etc/ssl/certs/example.com.pem
redirect scheme https if !{ ssl_fc }
default_backend web_servers
backend web_servers
balance roundrobin
option httpchk GET /health
http-check expect status 200
server web1 203.0.113.10:80 check inter 10s fall 3 rise 2
server web2 203.0.113.11:80 check inter 10s fall 3 rise 2
server web3 203.0.113.12:80 check inter 10s fall 3 rise 2
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 30s
stats admin if TRUECreate SSL certificate directory
Create the SSL certificate directory and add your certificate. For testing, you can use a self-signed certificate.
sudo mkdir -p /etc/ssl/certs
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /tmp/example.com.key \
-out /tmp/example.com.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=example.com"
sudo cat /tmp/example.com.crt /tmp/example.com.key | sudo tee /etc/ssl/certs/example.com.pem
sudo chmod 600 /etc/ssl/certs/example.com.pem
sudo chown root:root /etc/ssl/certs/example.com.pemConfigure keepalived on primary server
Configure keepalived on your primary load balancer server. This server will have higher priority and own the virtual IP initially.
global_defs {
notification_email {
admin@example.com
}
notification_email_from lb1@example.com
smtp_server localhost
smtp_connect_timeout 30
router_id LB_DEVEL
script_user root
enable_script_security
}
vrrp_script chk_haproxy {
script "/usr/bin/curl -f http://localhost:8404/stats || exit 1"
interval 2
weight -2
fall 3
rise 2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 110
advert_int 1
authentication {
auth_type PASS
auth_pass mySecurePass123
}
virtual_ipaddress {
203.0.113.100/24
}
track_script {
chk_haproxy
}
notify_master "/etc/keepalived/master.sh"
notify_backup "/etc/keepalived/backup.sh"
notify_fault "/etc/keepalived/fault.sh"
}Configure keepalived on backup server
Configure keepalived on your backup load balancer server with lower priority. Change the router ID and state to BACKUP.
global_defs {
notification_email {
admin@example.com
}
notification_email_from lb2@example.com
smtp_server localhost
smtp_connect_timeout 30
router_id LB_BACKUP
script_user root
enable_script_security
}
vrrp_script chk_haproxy {
script "/usr/bin/curl -f http://localhost:8404/stats || exit 1"
interval 2
weight -2
fall 3
rise 2
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass mySecurePass123
}
virtual_ipaddress {
203.0.113.100/24
}
track_script {
chk_haproxy
}
notify_master "/etc/keepalived/master.sh"
notify_backup "/etc/keepalived/backup.sh"
notify_fault "/etc/keepalived/fault.sh"
}Create keepalived notification scripts
Create scripts that run when keepalived state changes. These scripts help with monitoring and logging state transitions.
#!/bin/bash
echo "$(date): Became MASTER" >> /var/log/keepalived-state.log
systemctl restart haproxy#!/bin/bash
echo "$(date): Became BACKUP" >> /var/log/keepalived-state.log#!/bin/bash
echo "$(date): Entered FAULT state" >> /var/log/keepalived-state.logsudo chmod +x /etc/keepalived/*.sh
sudo touch /var/log/keepalived-state.logEnable IP forwarding
Enable IP forwarding on both load balancer servers to allow proper routing of the virtual IP traffic.
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv4.ip_nonlocal_bind = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -pConfigure firewall rules
Configure firewall rules to allow HAProxy, keepalived VRRP traffic, and health check communications.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 8404/tcp
sudo ufw allow from 203.0.113.0/24 to any port 112
sudo ufw allow in on eth0 to 224.0.0.18Start and enable services
Start HAProxy and keepalived services on both servers. Start HAProxy first, then keepalived.
sudo systemctl enable haproxy
sudo systemctl start haproxy
sudo systemctl enable keepalived
sudo systemctl start keepalivedCreate backend health check endpoint
Configure a simple health check endpoint on your backend servers. This example shows a basic health check for Nginx servers.
server {
listen 80;
server_name _;
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
location / {
return 200 "Backend server is running\n";
add_header Content-Type text/plain;
}
}sudo ln -s /etc/nginx/sites-available/health /etc/nginx/sites-enabled/
sudo systemctl reload nginxVerify your setup
Test your keepalived and HAProxy configuration with these verification commands.
# Check HAProxy status and backend health
sudo systemctl status haproxy
curl -s http://localhost:8404/stats | grep -A 10 web_servers
Check keepalived status and virtual IP
sudo systemctl status keepalived
ip addr show | grep 203.0.113.100
Test load balancing through virtual IP
curl -H "Host: example.com" http://203.0.113.100
Check keepalived logs
sudo journalctl -u keepalived -f
Verify VRRP advertisements
sudo tcpdump -i eth0 proto 112Test failover by stopping HAProxy on the master node and verifying the backup takes over:
# On master node - simulate failure
sudo systemctl stop haproxy
Check that backup became master
cat /var/log/keepalived-state.log
Verify VIP moved to backup
ip addr show | grep 203.0.113.100
Restart HAProxy and check failback
sudo systemctl start haproxyAdvanced health monitoring configuration
Configure advanced HAProxy health checks
Enhance backend monitoring with more sophisticated health checks that verify application functionality rather than just HTTP responses.
backend web_servers
balance roundrobin
option httpchk GET /api/health
http-check expect status 200
http-check expect string "status":"ok"
server web1 203.0.113.10:80 check inter 5s fall 2 rise 3 slowstart 60s maxconn 100
server web2 203.0.113.11:80 check inter 5s fall 2 rise 3 slowstart 60s maxconn 100
server web3 203.0.113.12:80 check inter 5s fall 2 rise 3 backupAdd custom health check script
Create a more comprehensive health check script that monitors multiple aspects of the load balancer health.
#!/bin/bash
Check HAProxy process
if ! pgrep haproxy > /dev/null; then
exit 1
fi
Check HAProxy stats endpoint
if ! curl -f -s http://localhost:8404/stats > /dev/null; then
exit 1
fi
Check if any backend servers are available
STATS=$(curl -s http://localhost:8404/stats)
AVAILABLE=$(echo "$STATS" | grep -c "web_servers.*UP")
if [ "$AVAILABLE" -lt 1 ]; then
exit 1
fi
exit 0sudo chmod +x /usr/local/bin/haproxy-health.shUpdate keepalived with enhanced monitoring
Replace the simple curl check with the comprehensive health script in your keepalived configuration.
vrrp_script chk_haproxy {
script "/usr/local/bin/haproxy-health.sh"
interval 3
weight -5
fall 2
rise 2
timeout 10
}Monitoring and alerting integration
For production deployments, integrate your keepalived cluster with monitoring systems like Prometheus and Grafana for HAProxy monitoring to track failover events, backend health, and performance metrics.
Configure log aggregation
Set up centralized logging to track keepalived state changes and HAProxy backend health across your cluster.
$ModLoad imudp
$UDPServerRun 514
$UDPServerAddress 127.0.0.1
local0.* /var/log/haproxy.log
& stopsudo systemctl restart rsyslogConsider implementing centralized logging with rsyslog to aggregate logs from all load balancer nodes for better operational visibility.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Virtual IP doesn't appear | VRRP authentication mismatch | Verify auth_pass matches on both nodes |
| Both nodes become MASTER | Network split-brain condition | Check network connectivity and VRRP multicast traffic |
| HAProxy backend shows DOWN | Backend health check failing | Verify /health endpoint returns 200 on backend servers |
| Keepalived logs connection refused | HAProxy stats not accessible | Check HAProxy stats socket permissions and binding |
| Failover not happening | Health check script not executable | sudo chmod +x /usr/local/bin/haproxy-health.sh |
| SSL certificate errors | Certificate file format or permissions | Verify PEM format and chmod 600 on certificate |
Next steps
- Set up HAProxy high availability with keepalived clustering for automatic failover
- Monitor HAProxy and Consul with Prometheus and Grafana dashboards
- Configure NGINX load balancing with health checks and automatic failover
- Implement HAProxy SSL termination with Let's Encrypt certificates for secure load balancing
- Set up keepalived cluster monitoring with Prometheus alerts and Grafana dashboards
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Global variables
SCRIPT_NAME=$(basename "$0")
PRIMARY_IP=""
BACKUP_IP=""
VIRTUAL_IP=""
INTERFACE="eth0"
DOMAIN="example.com"
NODE_TYPE=""
# Usage function
usage() {
echo "Usage: $SCRIPT_NAME [OPTIONS]"
echo "Options:"
echo " --primary-ip IP Primary load balancer IP"
echo " --backup-ip IP Backup load balancer IP"
echo " --virtual-ip IP Virtual IP address"
echo " --interface IF Network interface (default: eth0)"
echo " --domain DOMAIN Domain name (default: example.com)"
echo " --node-type TYPE Node type: primary or backup"
echo " --help Show this help"
exit 1
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--primary-ip) PRIMARY_IP="$2"; shift 2 ;;
--backup-ip) BACKUP_IP="$2"; shift 2 ;;
--virtual-ip) VIRTUAL_IP="$2"; shift 2 ;;
--interface) INTERFACE="$2"; shift 2 ;;
--domain) DOMAIN="$2"; shift 2 ;;
--node-type) NODE_TYPE="$2"; shift 2 ;;
--help) usage ;;
*) echo -e "${RED}Unknown option: $1${NC}"; usage ;;
esac
done
# Validate required arguments
if [[ -z "$PRIMARY_IP" || -z "$BACKUP_IP" || -z "$VIRTUAL_IP" || -z "$NODE_TYPE" ]]; then
echo -e "${RED}Error: Missing required arguments${NC}"
usage
fi
if [[ "$NODE_TYPE" != "primary" && "$NODE_TYPE" != "backup" ]]; then
echo -e "${RED}Error: node-type must be 'primary' or 'backup'${NC}"
exit 1
fi
# Cleanup function for rollback
cleanup() {
echo -e "${RED}Installation failed. Cleaning up...${NC}"
systemctl stop haproxy keepalived 2>/dev/null || true
}
trap cleanup ERR
# Check prerequisites
echo -e "${YELLOW}[1/10] Checking prerequisites...${NC}"
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Detect distribution
if [ ! -f /etc/os-release ]; then
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
FIREWALL_CMD="firewall-cmd"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
FIREWALL_CMD="firewall-cmd"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected distribution: $PRETTY_NAME${NC}"
# Update system packages
echo -e "${YELLOW}[2/10] Updating system packages...${NC}"
$PKG_UPDATE
# Install required packages
echo -e "${YELLOW}[3/10] Installing HAProxy and keepalived...${NC}"
$PKG_INSTALL haproxy keepalived curl openssl
# Create SSL certificate directory and certificate
echo -e "${YELLOW}[4/10] Creating SSL certificate...${NC}"
mkdir -p /etc/ssl/certs
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /tmp/${DOMAIN}.key \
-out /tmp/${DOMAIN}.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=${DOMAIN}" 2>/dev/null
cat /tmp/${DOMAIN}.crt /tmp/${DOMAIN}.key > /etc/ssl/certs/${DOMAIN}.pem
chmod 600 /etc/ssl/certs/${DOMAIN}.pem
chown root:root /etc/ssl/certs/${DOMAIN}.pem
rm -f /tmp/${DOMAIN}.key /tmp/${DOMAIN}.crt
# Configure HAProxy
echo -e "${YELLOW}[5/10] Configuring HAProxy...${NC}"
cat > /etc/haproxy/haproxy.cfg << EOF
global
daemon
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
log stdout local0
defaults
mode http
timeout connect 5000
timeout client 50000
timeout server 50000
option httplog
option dontlognull
option redispatch
retries 3
maxconn 2000
frontend web_frontend
bind *:80
bind *:443 ssl crt /etc/ssl/certs/${DOMAIN}.pem
redirect scheme https if !{ ssl_fc }
default_backend web_servers
backend web_servers
balance roundrobin
option httpchk GET /health
http-check expect status 200
server web1 ${PRIMARY_IP}:8080 check inter 10s fall 3 rise 2
server web2 ${BACKUP_IP}:8080 check inter 10s fall 3 rise 2
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 30s
stats admin if TRUE
EOF
# Configure keepalived based on node type
echo -e "${YELLOW}[6/10] Configuring keepalived for ${NODE_TYPE} node...${NC}"
if [[ "$NODE_TYPE" == "primary" ]]; then
ROUTER_ID="LB_PRIMARY"
STATE="MASTER"
PRIORITY="110"
EMAIL_FROM="lb-primary@${DOMAIN}"
else
ROUTER_ID="LB_BACKUP"
STATE="BACKUP"
PRIORITY="100"
EMAIL_FROM="lb-backup@${DOMAIN}"
fi
cat > /etc/keepalived/keepalived.conf << EOF
global_defs {
notification_email {
admin@${DOMAIN}
}
notification_email_from ${EMAIL_FROM}
smtp_server localhost
smtp_connect_timeout 30
router_id ${ROUTER_ID}
script_user root
enable_script_security
}
vrrp_script chk_haproxy {
script "/usr/bin/curl -f http://localhost:8404/stats || exit 1"
interval 2
weight -2
fall 3
rise 2
}
vrrp_instance VI_1 {
state ${STATE}
interface ${INTERFACE}
virtual_router_id 51
priority ${PRIORITY}
advert_int 1
authentication {
auth_type PASS
auth_pass mySecurePass123
}
virtual_ipaddress {
${VIRTUAL_IP}/24
}
track_script {
chk_haproxy
}
notify_master "/etc/keepalived/master.sh"
notify_backup "/etc/keepalived/backup.sh"
notify_fault "/etc/keepalived/fault.sh"
}
EOF
# Create keepalived notification scripts
echo -e "${YELLOW}[7/10] Creating keepalived notification scripts...${NC}"
cat > /etc/keepalived/master.sh << 'EOF'
#!/bin/bash
echo "$(date): Became MASTER" >> /var/log/keepalived-state.log
systemctl restart haproxy
EOF
cat > /etc/keepalived/backup.sh << 'EOF'
#!/bin/bash
echo "$(date): Became BACKUP" >> /var/log/keepalived-state.log
EOF
cat > /etc/keepalived/fault.sh << 'EOF'
#!/bin/bash
echo "$(date): Entered FAULT state" >> /var/log/keepalived-state.log
EOF
chmod 755 /etc/keepalived/*.sh
chown root:root /etc/keepalived/*.sh
# Configure firewall
echo -e "${YELLOW}[8/10] Configuring firewall...${NC}"
case "$PKG_MGR" in
apt)
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 8404/tcp
ufw allow 112/tcp
echo "y" | ufw enable 2>/dev/null || true
;;
dnf|yum)
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --permanent --add-port=8404/tcp
firewall-cmd --permanent --add-protocol=vrrp
firewall-cmd --reload
;;
esac
# Enable and start services
echo -e "${YELLOW}[9/10] Starting services...${NC}"
systemctl enable haproxy keepalived
systemctl start haproxy
systemctl start keepalived
# Verify installation
echo -e "${YELLOW}[10/10] Verifying installation...${NC}"
sleep 5
if ! systemctl is-active --quiet haproxy; then
echo -e "${RED}HAProxy service is not running${NC}"
exit 1
fi
if ! systemctl is-active --quiet keepalived; then
echo -e "${RED}Keepalived service is not running${NC}"
exit 1
fi
if ! curl -f http://localhost:8404/stats >/dev/null 2>&1; then
echo -e "${RED}HAProxy stats page is not accessible${NC}"
exit 1
fi
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "${GREEN}HAProxy stats available at: http://$(hostname -I | awk '{print $1}'):8404/stats${NC}"
echo -e "${GREEN}Virtual IP: ${VIRTUAL_IP}${NC}"
echo -e "${GREEN}Node type: ${NODE_TYPE}${NC}"
Review the script before running. Execute with: bash install.sh