How to Set Up a Highly Available Web Stack with Keepalived on RHEL 7

High availability eliminates single points of failure by ensuring that if one server goes down, another takes over seamlessly. For web servers, one of the most common HA patterns is a Virtual IP (VIP) address that floats between two or more nodes using the VRRP protocol — Virtual Router Redundancy Protocol. Keepalived implements VRRP on Linux and is the standard solution for floating VIPs in RHEL 7 environments. When the active (MASTER) server becomes unreachable, the standby (BACKUP) server claims the VIP within one or two seconds, and clients connecting to that IP experience only a brief TCP timeout rather than a prolonged outage. This guide walks through a complete Keepalived setup with Nginx health checking, failover testing, and VIP binding configuration.

Prerequisites

  • Two RHEL 7 servers on the same Layer 2 network segment (they must be able to send multicast or unicast VRRP packets to each other)
  • A Virtual IP address in the same subnet as the servers’ physical NICs (e.g., server1: 192.168.1.10, server2: 192.168.1.11, VIP: 192.168.1.100)
  • Nginx installed on both servers: yum install -y nginx
  • Root or sudo access on both servers
  • Firewall rules allowing VRRP traffic (protocol 112) between the two servers

Step 1: Installing Keepalived on Both Servers

Run the following on both servers:

yum install -y keepalived

Verify the installation:

keepalived --version
# Keepalived v1.3.x (DD/MM/YYYY)

systemctl enable keepalived

Open the required firewall ports on both servers. VRRP uses protocol 112 (not TCP or UDP):

firewall-cmd --permanent --add-rich-rule='rule protocol value="vrrp" accept'
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

Step 2: Configuring the MASTER Node

On server1 (the primary/MASTER), edit the Keepalived configuration file:

cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.orig
vi /etc/keepalived/keepalived.conf
! Keepalived Configuration — MASTER node (server1: 192.168.1.10)

global_defs {
    router_id server1
    notification_email {
        [email protected]
    }
    notification_email_from [email protected]
    smtp_server 127.0.0.1
    smtp_connect_timeout 30
}

vrrp_script check_nginx {
    script "/usr/bin/systemctl is-active nginx"
    interval 2        # check every 2 seconds
    weight  -20       # reduce priority by 20 if check fails
    fall     2        # require 2 consecutive failures before reducing priority
    rise     2        # require 2 successes before restoring priority
}

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

    authentication {
        auth_type PASS
        auth_pass S3cur3P@ss
    }

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

    track_script {
        check_nginx
    }
}

Key parameters explained:

  • state MASTER — this node starts as the active VIP holder
  • interface eth0 — the NIC on which VRRP packets are sent and the VIP is bound; replace with your actual interface name (check with ip link show)
  • virtual_router_id 51 — must be identical on MASTER and BACKUP; identifies this VRRP group (1–255)
  • priority 100 — MASTER should have higher priority than BACKUP (e.g., MASTER=100, BACKUP=90)
  • advert_int 1 — send VRRP advertisement every 1 second; BACKUP waits 3× this before taking over
  • auth_type PASS — simple password authentication between MASTER and BACKUP; must match on both nodes
  • virtual_ipaddress — the floating VIP; Keepalived will add/remove this with ip addr add/del

Step 3: Configuring the BACKUP Node

On server2 (the standby/BACKUP), create an identical configuration with two differences: state BACKUP and a lower priority:

vi /etc/keepalived/keepalived.conf
! Keepalived Configuration — BACKUP node (server2: 192.168.1.11)

global_defs {
    router_id server2
    notification_email {
        [email protected]
    }
    notification_email_from [email protected]
    smtp_server 127.0.0.1
    smtp_connect_timeout 30
}

vrrp_script check_nginx {
    script "/usr/bin/systemctl is-active nginx"
    interval 2
    weight  -20
    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 S3cur3P@ss
    }

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

    track_script {
        check_nginx
    }
}

Step 4: Enabling ip_nonlocal_bind for Nginx on the VIP

When Nginx starts, the VIP may not yet be assigned to the current server (especially at boot time when VRRP negotiation has not completed). Without the ip_nonlocal_bind kernel parameter, Nginx will fail to bind to the VIP address and refuse to start.

Enable this on both servers:

sysctl -w net.ipv4.ip_nonlocal_bind=1
echo 'net.ipv4.ip_nonlocal_bind = 1' >> /etc/sysctl.d/99-keepalived.conf
sysctl -p /etc/sysctl.d/99-keepalived.conf

Configure Nginx to listen on the VIP on both servers. Edit /etc/nginx/nginx.conf or a vhost file:

server {
    listen 192.168.1.100:80;
    listen 80;
    server_name example.com;
    root /var/www/html;
    index index.html;
}

Start Nginx on both servers:

systemctl enable nginx --now
systemctl status nginx

Step 5: Starting Keepalived and Verifying VIP Assignment

Start Keepalived on both servers (start MASTER first):

# On server1 (MASTER)
systemctl start keepalived
systemctl status keepalived

# On server2 (BACKUP)
systemctl start keepalived
systemctl status keepalived

Verify the VIP is assigned to the MASTER:

# On server1 — should show 192.168.1.100
ip addr show eth0

# On server2 — should NOT show 192.168.1.100
ip addr show eth0

Check Keepalived logs for VRRP state transitions:

journalctl -u keepalived -n 50 --no-pager

Look for lines like:

VRRP_Instance(VI_1) Transition to MASTER STATE
VRRP_Instance(VI_1) Sending gratuitous ARPs on eth0 for 192.168.1.100

Step 6: Testing Failover

Simulate a MASTER failure by stopping Keepalived on server1 while watching the VIP on server2:

# Terminal 1 — watch VIP on server2
watch -n 0.5 ip addr show eth0

# Terminal 2 — stop keepalived on server1
systemctl stop keepalived

Within 3 seconds (3× advert_int), server2 should claim the VIP. Restore the MASTER:

systemctl start keepalived

The VIP will return to server1 because its priority (100) is higher than server2’s (90). To prevent preemption and keep server2 as MASTER until explicitly failed back, add nopreempt to the BACKUP configuration’s vrrp_instance block.

Test the Nginx health check failover by stopping Nginx on the MASTER:

systemctl stop nginx

The vrrp_script check_nginx will detect the failure (after 2 intervals = 4 seconds), subtract 20 from the MASTER’s priority (making it 80, below the BACKUP’s 90), and trigger a VIP migration to server2. Restart Nginx to restore:

systemctl start nginx

Conclusion

Keepalived with VRRP provides a robust, lightweight high availability solution for RHEL 7 web stacks that requires no shared storage, no cluster middleware, and no application changes. By combining the floating VIP with a vrrp_script that monitors Nginx health, you get automatic failover both when the entire server goes down and when just the web server process fails. The ip_nonlocal_bind parameter ensures smooth startup regardless of which node currently holds the VIP. With failover times under five seconds and the ability to host the entire setup on two commodity servers, Keepalived is one of the most cost-effective HA tools available in the RHEL 7 ecosystem.