Set up OpenVPN high availability cluster with automatic failover and load balancing

Advanced 90 min Apr 10, 2026
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Build a production-ready OpenVPN high availability cluster using keepalived for automatic failover and HAProxy for load balancing across multiple OpenVPN servers.

Prerequisites

  • At least 4 servers with sudo access
  • Basic networking knowledge
  • SSH key authentication between servers
  • Domain name or static IP addresses

What this solves

A single OpenVPN server creates a point of failure that can disrupt remote access for your entire organization. This tutorial creates a highly available OpenVPN cluster with automatic failover using keepalived and load balancing through HAProxy, ensuring continuous VPN connectivity even when individual servers fail.

Step-by-step installation

Update system packages on all nodes

Start by updating packages on all three servers that will form your OpenVPN cluster.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install OpenVPN and Easy-RSA on all OpenVPN nodes

Install OpenVPN server and certificate management tools on your two OpenVPN backend servers (203.0.113.10 and 203.0.113.11).

sudo apt install -y openvpn easy-rsa
sudo mkdir -p /etc/openvpn/easy-rsa
sudo dnf install -y openvpn easy-rsa
sudo mkdir -p /etc/openvpn/easy-rsa

Set up Certificate Authority on primary node

Initialize the PKI infrastructure on your primary OpenVPN server (203.0.113.10). This creates shared certificates for the cluster.

cd /etc/openvpn/easy-rsa
sudo /usr/share/easy-rsa/easyrsa init-pki
sudo /usr/share/easy-rsa/easyrsa build-ca nopass

Generate the server certificate and Diffie-Hellman parameters.

sudo /usr/share/easy-rsa/easyrsa gen-req server nopass
sudo /usr/share/easy-rsa/easyrsa sign-req server server
sudo /usr/share/easy-rsa/easyrsa gen-dh
sudo openvpn --genkey secret ta.key

Configure shared certificate storage

Create a shared directory for certificates and copy them to both OpenVPN servers. We'll use rsync over SSH for synchronization.

sudo mkdir -p /etc/openvpn/certs
sudo cp pki/ca.crt /etc/openvpn/certs/
sudo cp pki/issued/server.crt /etc/openvpn/certs/
sudo cp pki/private/server.key /etc/openvpn/certs/
sudo cp pki/dh.pem /etc/openvpn/certs/
sudo cp ta.key /etc/openvpn/certs/

Copy certificates to the secondary OpenVPN server.

sudo rsync -av /etc/openvpn/certs/ root@203.0.113.11:/etc/openvpn/certs/

Configure OpenVPN server on primary node

Create the OpenVPN server configuration that will work with load balancing. Both servers use identical configs.

port 1194
proto udp
dev tun
ca /etc/openvpn/certs/ca.crt
cert /etc/openvpn/certs/server.crt
key /etc/openvpn/certs/server.key
dh /etc/openvpn/certs/dh.pem
tls-auth /etc/openvpn/certs/ta.key 0

server 10.8.0.0 255.255.255.0
ifconfig-pool-persist /var/log/openvpn/ipp.txt

push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"

keepalive 10 120
cipher AES-256-CBC
user nobody
group nogroup
persist-key
persist-tun

status /var/log/openvpn/openvpn-status.log
log-append /var/log/openvpn/openvpn.log
verb 3
explicit-exit-notify 1

management 127.0.0.1 7505

Configure OpenVPN server on secondary node

Copy the same configuration to the secondary server, but change the server pool to avoid IP conflicts.

sudo scp /etc/openvpn/server.conf root@203.0.113.11:/etc/openvpn/

Modify the server pool on the secondary node to use a different subnet.

server 10.8.1.0 255.255.255.0

Create OpenVPN log directories

Create logging directories on both OpenVPN servers.

sudo mkdir -p /var/log/openvpn
sudo chown nobody:nogroup /var/log/openvpn
sudo chmod 755 /var/log/openvpn

Enable IP forwarding on OpenVPN nodes

Enable packet forwarding to allow VPN traffic to route properly.

echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Configure firewall rules for OpenVPN

Set up iptables rules to allow OpenVPN traffic and NAT for client connections.

sudo iptables -A FORWARD -i tun+ -j ACCEPT
sudo iptables -A FORWARD -i tun+ -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o tun+ -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/16 -o eth0 -j MASQUERADE
sudo iptables -A INPUT -i tun+ -j ACCEPT
sudo iptables -A INPUT -p udp --dport 1194 -j ACCEPT

Save the firewall rules.

sudo apt install -y iptables-persistent
sudo netfilter-persistent save
sudo iptables-save | sudo tee /etc/sysconfig/iptables

Start and enable OpenVPN services

Enable OpenVPN to start automatically and start the service on both servers.

sudo systemctl enable openvpn@server
sudo systemctl start openvpn@server
sudo systemctl status openvpn@server

Install HAProxy and keepalived on load balancer node

Install the load balancing and high availability components on your third server (203.0.113.12).

sudo apt install -y haproxy keepalived
sudo dnf install -y haproxy keepalived

Configure HAProxy for UDP load balancing

Configure HAProxy to balance OpenVPN UDP traffic across your OpenVPN servers.

global
    log stdout local0
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user haproxy
    group haproxy
    daemon

defaults
    mode tcp
    log global
    option tcplog
    option dontlognull
    retries 3
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

listen stats
    bind *:8080
    stats enable
    stats uri /stats
    stats refresh 5s
    stats admin if TRUE

frontend openvpn_frontend
    bind *:1194
    mode tcp
    default_backend openvpn_backend

backend openvpn_backend
    mode tcp
    balance roundrobin
    option tcp-check
    tcp-check connect port 1194
    server ovpn1 203.0.113.10:1194 check inter 5000 fall 2 rise 2
    server ovpn2 203.0.113.11:1194 check inter 5000 fall 2 rise 2

Configure keepalived for automatic failover

Set up keepalived to provide a virtual IP that automatically fails over between HAProxy instances.

vrrp_script chk_haproxy {
    script "/bin/curl -f http://localhost:8080/stats || exit 1"
    interval 2
    weight -2
    fall 3
    rise 2
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 101
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass SecurePass123!
    }
    virtual_ipaddress {
        203.0.113.100/24
    }
    track_script {
        chk_haproxy
    }
    notify_master "/usr/local/bin/notify.sh MASTER"
    notify_backup "/usr/local/bin/notify.sh BACKUP"
    notify_fault "/usr/local/bin/notify.sh FAULT"
}

Create notification script for keepalived events

Create a script to log keepalived state changes and optionally send notifications.

#!/bin/bash
DATE=$(date '+%Y-%m-%d %H:%M:%S')
HOST=$(hostname)
STATE=$1

echo "$DATE: $HOST changed to $STATE state" >> /var/log/keepalived-notify.log

case $STATE in
    "MASTER") echo "$DATE: $HOST became MASTER" | logger -t keepalived
              # Optional: send email or webhook notification
              ;;
    "BACKUP") echo "$DATE: $HOST became BACKUP" | logger -t keepalived
              ;;
    "FAULT")  echo "$DATE: $HOST entered FAULT state" | logger -t keepalived
              ;;
    *)        echo "$DATE: $HOST entered unknown state: $STATE" | logger -t keepalived
              ;;
esac

Make the script executable.

sudo chmod 755 /usr/local/bin/notify.sh
sudo chown root:root /usr/local/bin/notify.sh

Set up secondary HAProxy node for redundancy

Install HAProxy and keepalived on a fourth server (203.0.113.13) for load balancer redundancy.

sudo scp /etc/haproxy/haproxy.cfg root@203.0.113.13:/etc/haproxy/
sudo scp /etc/keepalived/keepalived.conf root@203.0.113.13:/etc/keepalived/
sudo scp /usr/local/bin/notify.sh root@203.0.113.13:/usr/local/bin/

Modify the keepalived configuration on the secondary node to be a backup.

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    # ... rest identical to master config
}

Configure firewall rules for HAProxy nodes

Allow OpenVPN traffic and HAProxy management traffic.

sudo iptables -A INPUT -p tcp --dport 1194 -j ACCEPT
sudo iptables -A INPUT -p udp --dport 1194 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8080 -s 203.0.113.0/24 -j ACCEPT
sudo iptables -A INPUT -p vrrp -j ACCEPT
sudo iptables -A OUTPUT -p vrrp -j ACCEPT

Start and enable services on HAProxy nodes

Enable and start HAProxy and keepalived on both load balancer nodes.

sudo systemctl enable haproxy keepalived
sudo systemctl start haproxy keepalived
sudo systemctl status haproxy keepalived

Create client certificate automation script

Create a script to generate client certificates that work with the cluster.

#!/bin/bash

if [ $# -ne 1 ]; then
    echo "Usage: $0 "
    exit 1
fi

CLIENT="$1"
EASY_RSA="/etc/openvpn/easy-rsa"

cd $EASY_RSA

Generate client certificate

sudo /usr/share/easy-rsa/easyrsa gen-req "$CLIENT" nopass sudo /usr/share/easy-rsa/easyrsa sign-req client "$CLIENT"

Create client config

cat > "/tmp/${CLIENT}.ovpn" << EOF client dev tun proto udp remote 203.0.113.100 1194 resolv-retry infinite nobind user nobody group nogroup persist-key persist-tun remote-cert-tls server cipher AES-256-CBC verb 3 key-direction 1 $(sudo cat /etc/openvpn/certs/ca.crt) $(sudo cat pki/issued/${CLIENT}.crt) $(sudo cat pki/private/${CLIENT}.key) $(sudo cat /etc/openvpn/certs/ta.key) EOF echo "Client configuration created: /tmp/${CLIENT}.ovpn" echo "Copy this file to your client device."

Make the script executable.

sudo chmod 755 /usr/local/bin/create-ovpn-client.sh

Set up health monitoring scripts

Create monitoring scripts to check cluster health and log performance metrics.

#!/bin/bash

LOG_FILE="/var/log/ovpn-cluster-health.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')

Check virtual IP status

VIP_STATUS=$(ip addr show | grep 203.0.113.100 && echo "VIP Active" || echo "VIP Inactive")

Check HAProxy status

HAPROXY_STATUS=$(systemctl is-active haproxy)

Check OpenVPN backend connectivity

OVPN1_CHECK=$(nc -uz 203.0.113.10 1194 && echo "UP" || echo "DOWN") OVPN2_CHECK=$(nc -uz 203.0.113.11 1194 && echo "UP" || echo "DOWN")

Get connected client count from HAProxy stats

CLIENT_COUNT=$(curl -s http://localhost:8080/stats | grep -c "ESTABLISHED" || echo "0") echo "$DATE: VIP=$VIP_STATUS HAProxy=$HAPROXY_STATUS OVPN1=$OVPN1_CHECK OVPN2=$OVPN2_CHECK Clients=$CLIENT_COUNT" >> $LOG_FILE

Make executable and set up cron job.

sudo chmod 755 /usr/local/bin/monitor-ovpn-cluster.sh
echo "/5    * /usr/local/bin/monitor-ovpn-cluster.sh" | sudo crontab -

Configure shared certificate synchronization

Set up automated certificate sync

Create a systemd service to automatically synchronize certificates between OpenVPN nodes.

[Unit]
Description=OpenVPN Certificate Synchronization
After=network.target

[Service]
Type=oneshot
User=root
ExecStart=/usr/bin/rsync -av --delete /etc/openvpn/certs/ 203.0.113.11:/etc/openvpn/certs/
ExecStart=/usr/bin/rsync -av --delete /etc/openvpn/easy-rsa/pki/ 203.0.113.11:/etc/openvpn/easy-rsa/pki/

Create a timer to run the sync every hour.

[Unit]
Description=Run certificate sync every hour
Requires=ovpn-cert-sync.service

[Timer]
OnCalendar=hourly
Persistent=true

[Install]
WantedBy=timers.target

Enable the sync timer.

sudo systemctl daemon-reload
sudo systemctl enable ovpn-cert-sync.timer
sudo systemctl start ovpn-cert-sync.timer

Verify your setup

Test each component of your high availability OpenVPN cluster.

# Check OpenVPN services on both nodes
sudo systemctl status openvpn@server

Verify HAProxy is balancing traffic

curl -s http://203.0.113.100:8080/stats

Check keepalived status and virtual IP

sudo systemctl status keepalived ip addr show | grep 203.0.113.100

Test OpenVPN connectivity through load balancer

sudo /usr/local/bin/create-ovpn-client.sh testuser

Monitor cluster health

tail -f /var/log/ovpn-cluster-health.log

Check certificate synchronization

sudo systemctl status ovpn-cert-sync.timer
Note: The virtual IP (203.0.113.100) should only be active on one HAProxy node at a time. Clients connect to this IP and are automatically load balanced across healthy OpenVPN servers.

Configure cluster monitoring and alerting

Install monitoring components

For comprehensive cluster monitoring, consider integrating with Prometheus and Grafana or setting up Nagios monitoring.

#!/bin/bash

ALERT_EMAIL="admin@example.com"
SMTP_SERVER="mail.example.com"

Check if both OpenVPN servers are down

OVPN1_DOWN=$(! nc -uz 203.0.113.10 1194) OVPN2_DOWN=$(! nc -uz 203.0.113.11 1194) if [ "$OVPN1_DOWN" = "true" ] && [ "$OVPN2_DOWN" = "true" ]; then echo "CRITICAL: All OpenVPN servers are down!" | mail -s "OpenVPN Cluster Alert" -S smtp=$SMTP_SERVER $ALERT_EMAIL fi

Check keepalived failover

VIP_COUNT=$(ip addr show | grep -c 203.0.113.100) if [ "$VIP_COUNT" -eq 0 ]; then echo "CRITICAL: No server holds the virtual IP!" | mail -s "Keepalived Alert" -S smtp=$SMTP_SERVER $ALERT_EMAIL fi

Common issues

SymptomCauseFix
Clients can't connect to VIP Keepalived not running or misconfigured sudo systemctl status keepalived and check logs
Certificate mismatch errors Certificates not synchronized between nodes Run sudo systemctl start ovpn-cert-sync.service
HAProxy shows backends as down OpenVPN management interface not accessible Check management 127.0.0.1 7505 in OpenVPN config
Failover not working VRRP packets blocked by firewall Allow VRRP protocol: iptables -A INPUT -p vrrp -j ACCEPT
Split brain scenario Network partition between keepalived nodes Check network connectivity and increase advert_int
Load balancing uneven Different connection handling on OpenVPN nodes Verify identical configs and check server performance
Important: Always test failover scenarios in a maintenance window. Simulate server failures to ensure automatic recovery works as expected.

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle private cloud infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.