Building a Two-Node VPS Failover Cluster with Keepalived and VRRP

A single VPS is a single point of failure. Hardware failures, host crashes, and network outages all take your application offline. Keepalived uses the Virtual Router Redundancy Protocol (VRRP) to manage a floating IP address that automatically moves between two VPS nodes when one fails. This guide covers the complete setup: installation, configuration, health checks, and production-hardening steps.

How VRRP Failover Works

VRRP elects one node as the MASTER. That node owns the floating IP address and responds to ARP requests for it. The MASTER sends heartbeat advertisements to the BACKUP node every second. If the BACKUP stops receiving those advertisements for three consecutive intervals, it assumes the MASTER is dead, promotes itself, and takes over the floating IP. Clients connect to the floating IP and never need to know which physical node is behind it.

The key requirements are two VPS instances in the same layer-2 broadcast domain (same data center, same VLAN), and a spare public IP address that can be assigned to either node. Most providers offer floating IPs as an add-on service.

Step 1: Network Preparation

Before installing Keepalived, verify that both VPS nodes can reach each other on their private or public interfaces and that VRRP traffic is allowed.

# On both nodes, identify the interface for VRRP
ip addr show

# Usually eth0 or ens3. Note the IP address and subnet.

# Test connectivity between nodes
ping -c 3 <other-node-ip>

# Verify VRRP protocol (112) is not blocked
# On a modern system with nftables:
sudo nft list ruleset | grep vrrp

If your VPS provider blocks multicast, you will use unicast mode — covered later in this guide.

Step 2: Install Keepalived on Both Nodes

# Ubuntu/Debian
sudo apt update && sudo apt install keepalived -y

# RHEL/Rocky Linux
sudo dnf install keepalived -y

# Verify
keepalived --version
# Should show version 2.2.x or later

Step 3: Configure the MASTER Node

Create /etc/keepalived/keepalived.conf on the primary VPS:

global_defs {
    router_id VPS_NODE_1
    vrrp_skip_check_adv_addr
    vrrp_garp_master_refresh 60
}

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

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

    authentication {
        auth_type PASS
        auth_pass <generate-a-random-password>
    }

    virtual_ipaddress {
        203.0.113.100/24 dev eth0
    }

    track_script {
        chk_service
    }

    notify_master "/etc/keepalived/notify.sh MASTER"
    notify_backup "/etc/keepalived/notify.sh BACKUP"
    notify_fault  "/etc/keepalived/notify.sh FAULT"
}

Step 4: Configure the BACKUP Node

The BACKUP configuration is nearly identical, with a lower priority and BACKUP state:

global_defs {
    router_id VPS_NODE_2
    vrrp_skip_check_adv_addr
}

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

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

    authentication {
        auth_type PASS
        auth_pass <same-password-as-master>
    }

    virtual_ipaddress {
        203.0.113.100/24 dev eth0
    }

    track_script {
        chk_service
    }

    notify_master "/etc/keepalived/notify.sh MASTER"
    notify_backup "/etc/keepalived/notify.sh BACKUP"
    notify_fault  "/etc/keepalived/notify.sh FAULT"
}

Step 5: Health Check Script

The health check script determines whether the node is healthy enough to hold the floating IP. A simple but effective check tests whether your main service is running and responding:

#!/bin/bash
# /etc/keepalived/check_service.sh
# Exit 0 = healthy, exit 1 = unhealthy

# Check if nginx is running
if ! systemctl is-active --quiet nginx; then
    exit 1
fi

# Check if nginx responds on localhost
if ! curl -sf --max-time 2 http://localhost:80/health > /dev/null 2>&1; then
    exit 1
fi

exit 0
sudo chmod +x /etc/keepalived/check_service.sh

The weight of -30 means that when the check fails, the MASTER priority drops from 100 to 70. Since 70 is still above the BACKUP priority of 50, the MASTER retains the VIP unless two checks fail (dropping priority to 40, below the BACKUP). Adjust the weight and BACKUP priority to control how aggressively the cluster fails over.

Step 6: Notification Script

#!/bin/bash
# /etc/keepalived/notify.sh
STATE=$1
NOW=$(date -Iseconds)

echo "[$NOW] Keepalived state changed to: $STATE" | systemd-cat -t keepalived

# Send to a webhook (Discord, Slack, etc.)
curl -s -X POST "https://hooks.slack.com/services/YOUR/WEBHOOK/URL" \
    -H "Content-Type: application/json" \
    -d "{\"text\":\"VPS failover: Node $(hostname) is now $STATE\"}" \
    > /dev/null 2>&1 || true
sudo chmod +x /etc/keepalived/notify.sh

Step 7: Start and Test the Cluster

# Start Keepalived on both nodes
sudo systemctl enable --now keepalived

# Check status
sudo systemctl status keepalived

# Verify which node holds the VIP
ip addr show eth0 | grep 203.0.113.100

# Watch the logs during failover
sudo journalctl -u keepalived -f

Test the failover by stopping Keepalived on the MASTER:

# On the MASTER: simulate failure
sudo systemctl stop keepalived

# On the BACKUP: check that the VIP moved
ip addr show eth0 | grep 203.0.113.100
# Should show the VIP now

When you restart Keepalived on the original MASTER, it reclaims the VIP because its priority is higher.

Handling VPS Provider Limitations

  • Multicast blocked: Add unicast_peer { <peer-ip>; } inside the vrrp_instance block on both nodes. This sends VRRP advertisements directly to the peer IP instead of multicast address 224.0.0.18.
  • Floating IP not offered: Ask your provider if they support bringing your own IP or if they have an API to reassign IPs. Some providers require an API call rather than VRRP for IP failover.
  • ARP cache delays: After failover, the floating IP is associated with a new MAC address. Keepalived sends gratuitous ARP, but some switches and routers have ARP suppression. The vrrp_garp_master_refresh 60 directive sends periodic ARP refreshes to compensate.
  • Split-brain prevention: If the network link between nodes fails but both nodes remain up, each thinks the other is dead and both claim the VIP. Add a vrrp_script that pings the default gateway — if the gateway is unreachable, the node should drop to FAULT state.

Going Further: Active-Active with Multiple VRRP Instances

With two floating IPs and two VRRP instances, you can run both nodes as active servers. Node A is MASTER for VIP1 and BACKUP for VIP2. Node B is BACKUP for VIP1 and MASTER for VIP2. DNS round-robin or a load balancer distributes traffic across both VIPs. If either node fails, the other takes over both IPs. This doubles your capacity during normal operation while maintaining full redundancy.

For a deeper look at VPS plans that support floating IPs and high-availability setups, visit our VPS hosting comparison.

Leave a Reply