Keepalived High Availability VRRP Virtual IP on Linux VPS

Set Up Keepalived on VPS for High Availability

Updated: October 11, 2026 · High Availability · ~14 min read

Keepalived is a software solution that uses the VRRP (Virtual Router Redundancy Protocol) to create a Virtual IP Address (VIP) that floats between two or more servers. When the primary server (MASTER) fails, Keepalived automatically transfers the VIP to the standby server (BACKUP) within 1–3 seconds, ensuring your service remains online even when a server goes down.

This architecture is called Active-Passive HA — widely used with Nginx, HAProxy, MySQL, and web applications where zero downtime is a hard requirement.

Architecture Overview

Key components to understand before installation:

Note: Keepalived requires both servers to be on the same subnet (Layer 2 adjacency). VPS instances from the same provider in the same datacenter usually support this.

Prerequisites

Install Keepalived on Both Servers

Run this on both MASTER and BACKUP:

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

# Check version
keepalived --version

# Enable auto-start on boot
sudo systemctl enable keepalived

Enable IP Forwarding and Non-Local Bind

Keepalived needs these kernel options to bind the Virtual IP even when it is not natively assigned to the interface:

# On BOTH servers
sudo tee /etc/sysctl.d/99-keepalived.conf <<EOF
net.ipv4.ip_nonlocal_bind = 1
net.ipv4.ip_forward = 1
EOF

sudo sysctl --system

# Verify
sysctl net.ipv4.ip_nonlocal_bind
# Expected: net.ipv4.ip_nonlocal_bind = 1
Tip: ip_nonlocal_bind=1 is critical if you run Nginx/HAProxy on the VIP — without it, the service fails to start when the VIP is not yet assigned to this node.

Configure Keepalived on MASTER (192.168.1.10)

Create the config file on the MASTER node:

sudo nano /etc/keepalived/keepalived.conf
global_defs {
   notification_email {
     [email protected]
   }
   notification_email_from [email protected]
   smtp_server 127.0.0.1
   smtp_connect_timeout 30
   router_id MASTER_NODE
}

# Health check script for Nginx
vrrp_script chk_nginx {
    script "/usr/bin/pgrep nginx"
    interval 2        # Check every 2 seconds
    weight -20        # Drop priority by 20 on failure
    fall 2            # 2 consecutive failures = down
    rise 2            # 2 consecutive successes = up
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0         # Change to your actual interface (ip a)
    virtual_router_id 51   # Must match on both servers (1-255)
    priority 110           # MASTER must be higher than BACKUP
    advert_int 1           # Heartbeat interval (seconds)

    authentication {
        auth_type PASS
        auth_pass MyS3cr3t!   # Use the same password on both servers
    }

    virtual_ipaddress {
        192.168.1.100/24    # Virtual IP
    }

    track_script {
        chk_nginx
    }
}

Configure Keepalived on BACKUP (192.168.1.11)

The BACKUP config is similar but with different state and priority:

global_defs {
   router_id BACKUP_NODE
}

vrrp_script chk_nginx {
    script "/usr/bin/pgrep nginx"
    interval 2
    weight -20
    fall 2
    rise 2
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51      # Must match MASTER
    priority 100              # Lower than MASTER (110)
    advert_int 1

    authentication {
        auth_type PASS
        auth_pass MyS3cr3t!   # Exactly the same as MASTER
    }

    virtual_ipaddress {
        192.168.1.100/24
    }

    track_script {
        chk_nginx
    }
}

Start Keepalived

Start the service on both servers (MASTER first):

# On MASTER first
sudo systemctl start keepalived
sudo systemctl status keepalived

# Then on BACKUP
sudo systemctl start keepalived
sudo systemctl status keepalived

Test Failover

This is the most important step — always test before going live:

# From a client or third machine, verify the VIP is reachable
ping -c 3 192.168.1.100
curl http://192.168.1.100

# Check which server currently holds the VIP
# On MASTER:
ip addr show eth0 | grep 192.168.1.100
# Should show: inet 192.168.1.100/24

# Simulate failure: stop keepalived on MASTER
sudo systemctl stop keepalived

# Within 2-3 seconds, VIP should move to BACKUP
# On BACKUP:
ip addr show eth0 | grep 192.168.1.100
# Should show: inet 192.168.1.100/24

# Confirm service still responds
curl http://192.168.1.100

# Restore MASTER — VIP preempts back automatically
sudo systemctl start keepalived
Tip: To disable preemption (MASTER does not reclaim VIP when it comes back), add nopreempt to the MASTER's vrrp_instance block. Use this when stability matters more than strict active/passive behavior.

Custom HTTP Health Check Script

Instead of pgrep, write a script that tests an actual HTTP response:

sudo tee /etc/keepalived/check_nginx.sh <<'EOF'
#!/bin/bash
response=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/)
if [ "$response" = "200" ]; then
    exit 0
else
    exit 1
fi
EOF

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

Update the vrrp_script in your config:

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

Notify Script (Alert on Failover)

Keepalived can call a script whenever the state changes:

sudo tee /etc/keepalived/notify.sh <<'EOF'
#!/bin/bash
TYPE=$1
NAME=$2
STATE=$3

case $STATE in
  "MASTER")
    echo "[$(date)] MASTER: $NAME became MASTER" >> /var/log/keepalived-notify.log
    ;;
  "BACKUP")
    echo "[$(date)] BACKUP: $NAME became BACKUP" >> /var/log/keepalived-notify.log
    ;;
  "FAULT")
    echo "[$(date)] FAULT: $NAME entered FAULT state" >> /var/log/keepalived-notify.log
    ;;
esac
EOF

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

Add to vrrp_instance:

    notify /etc/keepalived/notify.sh

Verify Keepalived is Working Correctly

# 1. Check service status
sudo systemctl status keepalived

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

# 3. Confirm VIP is assigned to MASTER
ip addr show | grep "192.168.1.100"

# 4. Check current VRRP state
sudo journalctl -u keepalived --since "5 minutes ago" | grep -E "MASTER|BACKUP|FAULT"

# 5. View notify log
cat /var/log/keepalived-notify.log
Tip: Use tcpdump -i eth0 vrrp to capture VRRP advertisement packets and verify heartbeats are reaching both nodes — invaluable when debugging a failover that isn't triggering.

Troubleshooting Common Issues

Error: "VRRP_Instance(VI_1) Transition to MASTER STATE"

The BACKUP node promoted itself to MASTER because it stopped receiving advertisements from the real MASTER. This usually means the firewall is blocking VRRP traffic (protocol 112).

# Allow VRRP protocol on both servers
sudo ufw allow proto 112 from 192.168.1.0/24
# Or with iptables:
sudo iptables -A INPUT -p 112 -j ACCEPT
sudo iptables -A OUTPUT -p 112 -j ACCEPT

# Confirm VRRP packets are passing
sudo tcpdump -i eth0 vrrp -n

Error: "ip address associated with VRID not present in MASTER advert"

The virtual_router_id or virtual_ipaddress does not match between the two nodes.

# Check that virtual_router_id is identical on both servers
grep virtual_router_id /etc/keepalived/keepalived.conf

# Fix any mismatch, then restart:
sudo systemctl restart keepalived

Error: "VRRP_Script(chk_nginx) timed out"

The health check script is slower than the configured timeout, causing Keepalived to treat the service as failed.

vrrp_script chk_nginx {
    script "/etc/keepalived/check_nginx.sh"
    interval 3
    timeout 10          # Increase from default 5 to 10
    weight -20
    fall 3
    rise 2
}

sudo systemctl restart keepalived

Error: "SECURITY VIOLATION - same VRID" in logs

Another Keepalived instance in the same subnet is using the same virtual_router_id. Common on cloud/shared environments.

# Check for competing VRRP traffic on the subnet
sudo tcpdump -i eth0 vrrp -n | grep "vrid"

# Change virtual_router_id to a unique value on both servers
# e.g. from 51 to 73, then restart both

VIP does not failover even when MASTER service crashes

The health check script is returning exit code 0 even when Nginx is stopped. Test the script manually.

# Test manually
sudo /etc/keepalived/check_nginx.sh
echo "Exit code: $?"
# If Nginx is stopped but you get 0, the script has a bug

# Stop Nginx and watch for priority change:
sudo systemctl stop nginx
sudo journalctl -u keepalived -f
# Should see priority drop and transition to BACKUP

Frequently Asked Questions

Does Keepalived work on all VPS cloud providers?

Not all providers support VRRP/Gratuitous ARP that Keepalived requires. Some providers like AWS EC2 and GCP block multicast/VRRP traffic by default — those require alternative approaches such as API-driven route table failover. Standard VPS instances from providers that offer private networking within the same datacenter generally work well.

What is the difference between Keepalived and HAProxy?

Keepalived handles IP failover at the network layer (Virtual IP), while HAProxy handles load balancing at the application layer. They complement each other perfectly — use Keepalived to create a floating VIP and have HAProxy bind to that VIP for both HA and load balancing in a single setup.

When MASTER comes back online, will the VIP return automatically?

By default, Keepalived uses preemption — the MASTER reclaims the VIP immediately when its priority exceeds the BACKUP's. This can cause a brief second disruption. To keep the VIP on the BACKUP until a manual switch, add nopreempt to the MASTER's vrrp_instance config.