Load Balancer with HAProxy + Keepalived

HAProxy/Keepalived Setup

  • A cluster of two Load Balancer (LB) servers with HAProxy and Keepalived installed.
    • Keepalived ensures high availability by switching from MASTER to BACKUP if the MASTER goes down or HAProxy fails.
  • Two Web Servers (WS) running Apache.

In a real-world scenario, each LB should have two network interfaces:

  • Internal – for communication with web servers
  • External – for internet/public access

Web servers typically have one internal NIC only.

For training purposes, to simplify the setup, it’s recommended to use a single bridged adapter for all servers.

This allows:

  • All servers to be on the same subnet
  • Internet access from each server for package installation

⚠️ Note: This is not a production-grade setup, but it’s ideal for learning and testing the HAProxy + Keepalived architecture.

For srtup instructions you can use the article Infrastructure 1 NIC.

Install Required Packages

Initial Setup on All 4 Servers, install EPEL repo:

				
					sudo dnf install -y epel-release
				
			

On Load Balancers (LB1 & LB2). Install HAProxy and Keepalived:

				
					sudo dnf install -y haproxy keepalived 
				
			

On Web Servers (Web1 & Web2). Install and configure Apache:

				
					sudo dnf install -y httpd
sudo systemctl enable --now httpd
echo "Hello from Web1" | sudo tee /var/www/html/index.html   # on Web1
echo "Hello from Web2" | sudo tee /var/www/html/index.html   # on Web2

				
			

Configuration

Configure HAProxy on Both LB1 and LB2. Edit /etc/haproxy/haproxy.cfg:

				
					frontend http_front
    bind 192.168.1.100:80
#You should paste your public IP or any free IP in youe local network for training purposes.
    default_backend http_back

backend http_back
    balance roundrobin
#Here you should paste your web servers IP’s.
    server web1 192.168.1.21:80 check
    server web2 192.168.1.22:80 check

				
			

It should look like this:

Check HAProxy configuration:

				
					sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# Expected output: Configuration file is valid

				
			

Configure Keepalived. Backup original config:

				
					sudo cp /etc/keepalived/keepalived.conf  etc/keepalived/keepalived.conf.back
				
			

LB1 (MASTER) Configuration. Clear and replace with:

				
					global_defs {
    script_user root
    enable_script_security
}

vrrp_script chk_haproxy {
    script "/usr/bin/pidof haproxy"
    interval 2
    timeout 5
    fall 2
    rise 1
}

vrrp_instance VI_1 {
    state MASTER
    interface enp0s3               
    virtual_router_id 51
    priority 100                    
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1234
    }
    virtual_ipaddress {
        192.168.1.100
    }

    track_script {
        chk_haproxy
    }

    notify_master "/etc/keepalived/on-master.sh"
    notify_backup "/etc/keepalived/on-backup.sh"
}

				
			

LB2 (BACKUP) Configuration:

				
					vrrp_instance VI_1 {
    state BACKUP
    interface enp0s3                
    virtual_router_id 51
    priority 90                     
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1234
    }
    virtual_ipaddress {
        192.168.1.100
    }


    notify_master "/etc/keepalived/on-master.sh"
    notify_backup "/etc/keepalived/on-backup.sh"
}

				
			

Note: Do not include track_script on the BACKUP node, as HAProxy is not active there unless it becomes MASTER.

Additional Reading

Detailed explanation of Keepalived configuration check here.
HAProxy + Keepalived for Ceph Object Gateway check here.

Keepalived allows you to execute scripts when its state changes:

Transition           Script Triggered

→ MASTER         notify_master

→ BACKUP         notify_backup

→ FAULT             notify_fault (optional)

Create the Transition Scripts on-master.sh:

				
					sudo touch /etc/keepalived/on-master.sh

#Paste the following:
#!/bin/bash
systemctl start haproxy

				
			

on-backup.sh:

				
					sudo touch /etc/keepalived/on-backup.sh

#Paste the following:
#!/bin/bash
systemctl stop haproxy

				
			

Make them executable:

				
					sudo chmod +x /etc/keepalived/on-*.sh
				
			

 FLOW OF WORK

 Normal operation

  1. Keepalived starts on lb1
  2. It becomes MASTER (highest priority)
  3. Assigns VIP 192.168.1.100 to enp0s3
  4. Runs /etc/keepalived/on-master.sh → starts HAProxy
  5. Sends VRRP heartbeats every 1s to BACKUP

If HAProxy crashes

  1. chk_haproxy fails (e.g., pidof haproxy returns nothing)
  2. After 2 failures (fall 2), Keepalived marks node unhealthy
  3. Steps down → runs /etc/keepalived/on-backup.sh (stops HAProxy)
  4. VIP is removed
  5. BACKUP node sees no heartbeats → becomes MASTER
  6. BACKUP assigns VIP, starts HAProxy

 

If you include this track_script on the BACKUP node while HAProxy is stopped, it will always fail. The BACKUP node doesn’t need to track HAProxy because HAProxy only runs when the node becomes MASTER.

However, it does require the notify_master script so that, upon becoming MASTER, it can start HAProxy. It also needs the notify_backup script so that if it steps down, or if a server with a higher priority becomes active (such as the repaired original MASTER), it can stop HAProxy accordingly.

Firewall Configuration

On both LB1 and LB2:

				
					sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --zone=external --add-port=80/tcp --permanent
sudo firewall-cmd --permanent --add-rich-rule='rule protocol value="vrrp" accept'
sudo firewall-cmd –reload

				
			

On all servers:

				
					sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd –reload

				
			

On both LB1 and LB2 enable Non-local Bind to allow HAProxy to bind to the VIP before it’s assigned:

				
					sudo vi /etc/sysctl.conf
# Add:
net.ipv4.ip_nonlocal_bind = 1
# Apply:
sudo sysctl -p

				
			

Start the Services

On LB1:

				
					sudo systemctl enable --now keepalived
sudo systemctl enable --now haproxy

				
			

On LB2:

				
					sudo systemctl enable --now keepalived
				
			

Verify the VIP on LB1:

				
					ip a
				
			

You should see the VIP assigned to enp0s3. Something like this:

Testing

Open your browser and enter the VIP address.

Refresh the page several times — you should see the message alternate between “Hello from Web1” and “Hello from Web2”, indicating that HAProxy is successfully load balancing between the web servers.

Now, test failover by stopping Keepalived or HAProxy on LB1:

				
					sudo systemctl stop haproxy
				
			

The VIP should automatically move to LB2, and HAProxy on LB2 should start working.

To switch back to LB1, simply start HAProxy again:

				
					sudo systemctl start haproxy
				
			

Since LB1 has a higher priority, the VIP will float back to it automatically.