A highly available web stack ensures that your application remains accessible even when one of your servers fails. Keepalived implements the Virtual Router Redundancy Protocol (VRRP) on Linux, allowing a floating Virtual IP (VIP) address to migrate automatically between a master and one or more backup servers within seconds of a failure. When combined with Nginx or Apache as the web layer, Keepalived provides a lightweight, dependency-free HA solution that requires no shared storage or external cluster manager. This tutorial deploys a two-node Keepalived setup on RHEL 8 with a script-based health check that demotes the master if Nginx becomes unresponsive.

Prerequisites

  • Two RHEL 8 servers on the same Layer 2 network segment (e.g., 192.168.1.101 master, 192.168.1.102 backup)
  • A free VIP address on the same subnet (e.g., 192.168.1.100)
  • Root or sudo access on both nodes
  • Nginx installed and running on both nodes (sudo dnf install -y nginx)
  • The same shared network interface name on both nodes (verify with ip link; this tutorial uses eth0)

Step 1 — Install Keepalived on Both Nodes

Run the following commands on both the master and backup servers.

# Install keepalived on both nodes
sudo dnf install -y keepalived

# Enable the service to start at boot
sudo systemctl enable keepalived

# Allow VRRP multicast through the firewall on both nodes
sudo firewall-cmd --add-rich-rule='rule protocol value="112" accept' --permanent
sudo firewall-cmd --reload

Step 2 — Create the Health Check Script

Keepalived can execute a script periodically and use its exit code to adjust the node’s VRRP priority. A non-zero exit code lowers the priority below the backup’s value, triggering a failover. Create the same script on both nodes.

sudo tee /etc/keepalived/check_nginx.sh > /dev/null <<'EOF'
#!/bin/bash
# Returns 0 if nginx is running, 1 otherwise
if systemctl is-active --quiet nginx; then
    exit 0
else
    exit 1
fi
EOF

sudo chmod +x /etc/keepalived/check_nginx.sh

Step 3 — Configure the Master Node

Replace eth0 with your actual interface name if different. The master uses state MASTER and a higher priority value (101). The virtual_router_id must be identical on both nodes and unique on the network segment.

sudo tee /etc/keepalived/keepalived.conf > /dev/null <<'EOF'
global_defs {
   router_id MASTER_NODE
   script_user root
   enable_script_security
}

vrrp_script check_nginx {
    script "/etc/keepalived/check_nginx.sh"
    interval 2
    weight -30
    fall 2
    rise 2
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 101
    advert_int 1
    nopreempt

    authentication {
        auth_type PASS
        auth_pass S3cur3VRRP!
    }

    virtual_ipaddress {
        192.168.1.100/24 dev eth0 label eth0:vip
    }

    track_script {
        check_nginx
    }
}
EOF

sudo systemctl start keepalived
sudo systemctl status keepalived

Step 4 — Configure the Backup Node

The backup configuration is identical except for state BACKUP and a lower priority value (90). Run this block on the backup server only.

sudo tee /etc/keepalived/keepalived.conf > /dev/null <<'EOF'
global_defs {
   router_id BACKUP_NODE
   script_user root
   enable_script_security
}

vrrp_script check_nginx {
    script "/etc/keepalived/check_nginx.sh"
    interval 2
    weight -30
    fall 2
    rise 2
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 90
    advert_int 1

    authentication {
        auth_type PASS
        auth_pass S3cur3VRRP!
    }

    virtual_ipaddress {
        192.168.1.100/24 dev eth0 label eth0:vip
    }

    track_script {
        check_nginx
    }
}
EOF

sudo systemctl start keepalived
sudo systemctl status keepalived

Verify that the VIP is currently assigned to the master by running ip addr show eth0 on both nodes. Only the master should show the 192.168.1.100/24 address.

Step 5 — Test Failover

Simulate a master failure by stopping Keepalived or Nginx on the master node and observing the VIP migrate to the backup within two advertisement intervals (approximately two seconds).

# --- On the MASTER node ---
# Stop nginx to trigger the health check failure
sudo systemctl stop nginx

# Watch keepalived logs in real time
sudo journalctl -u keepalived -f &

# --- On the BACKUP node ---
# Within ~4 seconds, the VIP should appear
watch -n1 "ip addr show eth0 | grep '192.168.1.100'"

# --- Restore the master ---
# On the master, restart nginx
sudo systemctl start nginx
# Because nopreempt is set, the VIP stays on the backup
# Remove nopreempt and reload if you want the master to reclaim the VIP
sudo systemctl reload keepalived

Step 6 — Validate with curl

Confirm that web traffic is served through the VIP and continues to be served after failover.

# From a client machine, continuously poll the VIP
while true; do
    curl -s -o /dev/null -w "%{http_code} from %{remote_ip}n" http://192.168.1.100/
    sleep 1
done

During failover you should see at most one or two failed requests as the ARP cache on your gateway refreshes to point to the backup node’s MAC address. All subsequent requests should return 200 from the backup.

Conclusion

You have deployed a two-node highly available web stack on RHEL 8 using Keepalived and VRRP. The master holds the Virtual IP address and serves traffic under normal conditions. When the Nginx health check fails — or when Keepalived itself stops — the VIP migrates to the backup node within seconds, maintaining service continuity. The nopreempt directive prevents unnecessary VIP flapping when the master recovers. For production deployments, use a strong unique authentication password, restrict VRRP multicast with firewall rules, and log state transitions to a monitoring system.

Next steps: How to Install and Configure Traefik Reverse Proxy on RHEL 8, How to Configure HAProxy for TCP and HTTP Load Balancing on RHEL 8, and How to Set Up Nginx as a Layer 7 Load Balancer on RHEL 8.