MARWAN docs NOC knowledge base
DocsRouting and high availability

Set up keepalived with NGINX for a web server with HA (High Availability)

Run a highly available NGINX web server with keepalived, using IPv4 and IPv6 health-check scripts.

Updated Sami Ait Ali Oulahcen

1. http-ipv4-keepalive-and-restart#

This bash script will check if the web server is responding correctly via IPv4, and return an error if not. The keepalived process will use the output to switch to the backup instance(s) if there's an issue.

#!/bin/bash
# http-ipv4-keepalive-and-restart
# © 2025 by Sami Ait Ali Oulahcen
# Licensed under CC BY-SA 4.0
#

# If nginx returns HTTP code 200, return 0
sh -c '[ $(curl -4 --connect-timeout 3 -o /dev/null -I -L -s -w "%{http_code}" http://localhost) -eq 200 ]' && echo 0

# Else restart nginx and return error code
if [ $? -ne 0 ]; then
systemctl restart nginx
exit 1
fi

# keepalived will use this to switch to other instances

2. http-ipv6-keepalive-and-restart#

This bash script will check if the web server is responding correctly via IPv6, and return an error if not. The keepalived process will use the output to switch to the backup instance(s) if there's an issue.

#!/bin/bash
# http-ipv6-keepalive-and-restart
# © 2025 by Sami Ait Ali Oulahcen
# Licensed under CC BY-SA 4.0
#

# If nginx returns HTTP code 200, return 0
sh -c '[ $(curl -6 --connect-timeout 3 -o /dev/null -I -L -s -w "%{http_code}" http://localhost) -eq 200 ]' && echo 0

# Else restart nginx and return error code
if [ $? -ne 0 ]; then
systemctl restart nginx
exit 1
fi

# keepalived will use the error output to switch to other instances

3. Install and configure keepalived#

Install keepalived

dnf install keepalived

Let's configure keepalived. This is the master node's config:

/etc/keepalived/keepalived.conf
vrrp_script web-check-status-ipv4 {
	script "/usr/local/bin/web-check-status-ipv4.sh"
	interval 3
	timeout 5
	rise 27
	fall 2
	user nginx
}

vrrp_script web-check-status-ipv6 {
        script "/usr/local/bin/web-check-status-ipv6.sh"
        interval 3
        timeout 5
        rise 27
        fall 2
        user nginx
}

vrrp_instance web1-ipv4 {
	state MASTER
	interface ens18
	virtual_router_id 10
	priority 30
	unicast_src_ip 192.203.0.15
	unicast_peer {
		192.203.0.115
	}
	authentication {
        	auth_type PASS
        	auth_pass uvy23141276gfba5asc421baspz
	}
	virtual_ipaddress {
		192.203.0.20
	}
	virtual_ipaddress_excluded {
		2001:db8:dd::20
	}
	track_script {
		web-check-status-ipv4
	}
}

vrrp_instance web1-ipv6 {
        state MASTER
        interface ens18
        virtual_router_id 10
        priority 30
        unicast_src_ip 2001:db8:dd::15
        unicast_peer {
                2001:db8:dd::115
        }
        authentication {
                auth_type PASS
                auth_pass uvy23141276gfba5asc421baspz
        }
        virtual_ipaddress {
                2001:db8:dd::20
        }
        track_script {
                web-check-status-ipv6
        }
}

The scripts /usr/local/bin/web-check-status-ipv{4,6}.sh are the ones from chapter 1 and 2. I've configured keepalived to fall after 2 failed attempts fall 2 and to rise after 27 successful attempts rise 27 for more stability. You can customise to your liking.

And this is the backup node's config:

/etc/keepalived/keepalived.conf
vrrp_script web-check-status-ipv4 {
	script "/usr/local/bin/web-check-status-ipv4.sh"
	interval 3
	timeout 5
	rise 27
	fall 2
	user nginx
}

vrrp_script web-check-status-ipv6 {
        script "/usr/local/bin/web-check-status-ipv6.sh"
        interval 3
        timeout 5
        rise 27
        fall 2
        user nginx
}

vrrp_instance web1-ipv4 {
	state BACKUP
	interface ens18
	virtual_router_id 10
	priority 20
	unicast_src_ip 192.203.0.115
	unicast_peer {
		192.203.0.15
	}
	authentication {
        	auth_type PASS
        	auth_pass uvy23141276gfba5asc421baspz
	}
	virtual_ipaddress {
		192.203.0.20
	}
	virtual_ipaddress_excluded {
		2001:db8:dd::20
	}
	track_script {
		web-check-status-ipv4
	}
}

vrrp_instance web1-ipv6 {
        state BACKUP
        interface ens18
        virtual_router_id 10
        priority 20
        unicast_src_ip 2001:db8:dd::115
        unicast_peer {
                2001:db8:dd::15
        }
        authentication {
                auth_type PASS
                auth_pass uvy23141276gfba5asc421baspz
        }
        virtual_ipaddress {
                2001:db8:dd::20
        }
        track_script {
                web-check-status-ipv6
        }
}

4. Configure rsyncd to sync websites contents#

Install the rsync daemon

dnf install rsync-daemon

Then modify the configuration file /etc/rsyncd.conf with the path to your websites

/etc/rsyncd.conf
use chroot = false
[www]
uid = nobody
gid = nobody
path = /var/www
list = yes
read only = true

Allow rsyncd through the firewall, and SElinux

setsebool -P rsync_export_all_ro 1
firewall-cmd --permanent --add-service=rsynd
firewall-cmd --reload

And finally start the service

systemctl enable --now rsyncd

On the backup server, you can run something like this via cron to sync periodically:

rsync -a --delete rsync://web00.uni.ma/www/ /var/www