Keepalived and Virtual IPs: High Availability Without a Load Balancer

One floating address, two machines, and a failover that nobody notices

There is a particular flavour of homelab anxiety that arrives the moment a single box becomes load-bearing. Your reverse proxy, your DNS resolver, your little MQTT broker that the whole house now depends on — all of it pinned to one IP address on one machine that will, one day, want a kernel update at the worst possible time. The cloud answer to this is a managed load balancer, and it is a fine answer if you enjoy paying monthly for a TCP forwarder. The self-hosted answer, and a remarkably good one, is keepalived.

Keepalived implements VRRP — the Virtual Router Redundancy Protocol — which is the boring, battle-tested mechanism that lets two or more machines share a single floating IP. One node holds the address; if it falls over, another grabs it within a second or two. Your clients keep talking to the same IP and never know anything happened. No load balancer, no DNS round-robin with its glacial TTLs, no application-layer cleverness. Just an IP that refuses to die.

Advertisement

Each participating node runs keepalived and joins a virtual router identified by a number (the VRID). Within that group, nodes elect a MASTER based on a priority value — highest wins. The MASTER claims the virtual IP (VIP) and broadcasts VRRP advertisements over multicast, roughly once a second by default. The BACKUP nodes sit quietly and listen.

The instant the BACKUP stops hearing those advertisements — because the MASTER crashed, lost its link, or someone tripped over a cable — it promotes itself, claims the VIP, and fires off a gratuitous ARP so the switch updates its MAC table immediately. That gratuitous ARP is the magic: it tells the network “the IP you knew lives at this MAC now,” and traffic redirects without waiting for ARP caches to expire.

Say you have two nginx boxes, 10.0.0.11 and 10.0.0.12, and you want them to share 10.0.0.10. On the primary, /etc/keepalived/keepalived.conf:

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

    authentication {
        auth_type PASS
        auth_pass changeme-or-vrrpv3
    }

    virtual_ipaddress {
        10.0.0.10/24
    }
}

On the secondary, the file is identical except state BACKUP and priority 100. The lower priority means it only takes over when the primary is gone. Start the service on both:

$ sudo systemctl enable --now keepalived
$ ip addr show eth0 | grep 10.0.0.10
    inet 10.0.0.10/24 scope global secondary eth0

That secondary flag on the master is keepalived having added the VIP. Pull the plug on the master and watch the journal on the backup:

$ journalctl -u keepalived -f
Keepalived_vrrp[1421]: (VI_1) Backup received priority 0 advertisement
Keepalived_vrrp[1421]: (VI_1) Entering MASTER STATE
Keepalived_vrrp[1421]: (VI_1) setting VIPs.
Keepalived_vrrp[1421]: Sending gratuitous ARP on eth0 for 10.0.0.10

Under a second. Your ping 10.0.0.10 might drop a single packet.

The naive setup above fails over only when the whole host dies. But the far more common failure is the service dying while the host stays up — nginx segfaults, the VIP stays put, and you’re serving connection-refused with a healthy-looking master. Fix this with a vrrp_script that polls the thing you actually care about:

vrrp_script chk_nginx {
    script "/usr/bin/killall -0 nginx"
    interval 2
    weight -40
    fall 2
    rise 2
}

vrrp_instance VI_1 {
    # ... as above ...
    track_script {
        chk_nginx
    }
}

killall -0 doesn’t kill anything — it just tests whether the process exists, returning non-zero if not. When the check fails twice, keepalived subtracts 40 from the master’s priority (150 → 110), which is still above the backup’s 100, so be careful: make the weight large enough to actually demote below your backups. Set weight -60 here and the master drops to 90, the backup wins, and failover happens on a dead daemon, not just a dead box.

VRRP uses multicast, so it wants a flat layer-2 segment. It will not traverse a router without help, and some cloud networks and managed switches block multicast or VRRP outright — AWS and most VPS providers among them, which is precisely why they sell you load balancers. Two nodes both believing they’re MASTER (a split brain) will both ARP for the VIP and cause chaos; this almost always means they can’t see each other’s multicast, so verify with tcpdump -i eth0 vrrp before blaming keepalived. And the VIP gives you availability, not capacity — only one node serves traffic at a time. If you need to spread load, you still want something in front.

For a homelab or a small on-prem fleet where you control the network and just want a service that survives a reboot, keepalived is close to perfect: a hundred lines of config, no moving parts, no recurring bill, and failover faster than a human can notice. If you’re on a cloud VPS that filters multicast, or you genuinely need to balance load across nodes rather than just survive one dying, this isn’t your tool. But for “make this one IP immortal,” nothing beats it for effort-to-payoff.

Advertisement

Related Content

Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.