Skip to main content
RunBook Academy

LinuxLVI · Keepalived and VRRPVIP mechanics

Virtual IP mechanics - what actually moves during a failover

Intermediate⏱ ~14 minkeepalivediproute2tcpdump

What you'll learn

  • Describe what happens on the wire when a VIP transitions
  • Diagnose a VIP that moved while traffic did not follow
  • Explain the virtual MAC trade-off
  • Account for connection state that does not survive failover

Prerequisites

Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-11

Not yet marked complete on this device.

A failover is usually declared successful the moment the VIP shows up on the backup node. That check is necessary and it is not sufficient: the address moving and the traffic moving are two different events, separated by every ARP cache on the segment.

What keepalived actually does

On transition to MASTER, keepalived performs two operations:

  1. Adds the address. The equivalent of ip addr add 203.0.113.10/24 dev eth0. This is entirely local - no other machine learns anything from it.
  2. Announces it at layer 2. For IPv4, a gratuitous ARP (GARP): an ARP message the node sends unprompted, saying “203.0.113.10 is at this MAC”. For IPv6, an unsolicited neighbour advertisement.

Step 1 is what ip addr show reveals, and it is the step every failover check looks at. Step 2 is the one that makes the failover visible to anyone else, and it is the one that fails silently.

Why the announcement is necessary

Two separate caches have to be corrected, and neither expires quickly:

  • Switch MAC address tables. A switch forwards a frame to the port where it last saw that destination MAC as a source. Until the new node sends something from the VIP’s MAC, frames for that MAC keep going to the old port.
  • Host and router neighbour caches. Every client that has spoken to the VIP holds an IP-to-MAC mapping. Linux keeps entries usable for minutes after last use; network appliances and some embedded stacks keep them far longer.

The GARP updates both at once: it is broadcast, it carries the new MAC as the source, and receivers overwrite the mapping.

Read-only / Safethe gratuitous ARPs, sent from the new master on transition
# tcpdump -ni eth0 -c 8 arp or icmp6
14:22:07.118431 ARP, Reply 203.0.113.10 is-at 52:54:00:aa:bb:02, length 28
14:22:07.118552 ARP, Reply 203.0.113.10 is-at 52:54:00:aa:bb:02, length 28
14:22:07.118661 ARP, Reply 203.0.113.10 is-at 52:54:00:aa:bb:02, length 28
14:22:07.118742 ARP, Reply 203.0.113.10 is-at 52:54:00:aa:bb:02, length 28
14:22:07.118829 ARP, Reply 203.0.113.10 is-at 52:54:00:aa:bb:02, length 28

Illustrative output

Seeing these on the segment is the real evidence that a failover happened. Not seeing them, while ip addr shows the VIP present, is the signature of the failure below.

The VIP moved and traffic did not

# From a third host: what does the segment believe?
ip neigh show 203.0.113.10

# Force a fresh resolution and see who answers
sudo ip neigh flush dev eth0 203.0.113.10
ping -c 2 203.0.113.10
ip neigh show 203.0.113.10

If the neighbour entry names the old node’s MAC after a flush and a fresh ARP exchange, the old node is still answering for the VIP - which is a dual-MASTER situation, not a stale cache. linux-keepalived-limitations covers that path.

Tuning the announcement

Keepalived exposes the GARP behaviour, because one packet at one moment is fragile:

DirectiveEffect
garp_master_delaySeconds to wait after becoming master before sending the GARPs
garp_master_repeatHow many gratuitous ARPs to send
garp_master_refreshSeconds between periodic repeat announcements while master

garp_master_refresh is the one worth setting in an environment that has ever surprised you. A single burst at transition time can be lost; a periodic refresh every 30 or 60 seconds re-asserts the mapping continuously, so a device that missed the transition corrects itself within a minute instead of whenever its cache ages out.

garp_master_delay matters where the old master needs a moment to release the address. Announcing before it has stopped answering produces a brief period where both nodes reply to ARP for the same address, and whichever reply arrives last wins per client.

The virtual MAC trade-off

VRRP as specified uses a virtual MAC derived from the router id: 00:00:5e:00:01:<vrid>. The MAC moves with the address, so no client’s IP-to-MAC mapping ever becomes wrong - only the switch’s port mapping has to update.

Keepalived does not do this by default. It keeps the real interface MAC and relies on GARP to correct every cache. The use_vmac directive switches to the specification behaviour by creating a macvlan interface for the VIP.

The trade-off is real in both directions:

  • With use_vmac: client caches never go stale, so failover is robust against hosts that ignore GARP. But the segment now sees a MAC that moves between switch ports, which collides with port security, some bonding configurations, and most cloud virtual networks.
  • Without it (the default): nothing unusual on the wire, but correctness depends on every client honouring an unsolicited ARP.

Neither is wrong. Choose based on what your switching layer permits, and if you enable use_vmac, verify the switch has not learned the MAC as a security violation.

Binding to an address you do not hold

Services like HAProxy want to bind the VIP at startup, and on the backup node that address is absent. By default the bind fails and the unit will not start, so the service is not ready at the moment it becomes master.

# Allow binding to a non-local address
sudo sysctl -w net.ipv4.ip_nonlocal_bind=1
sudo sysctl -w net.ipv6.ip_nonlocal_bind=1

# Persist it
echo 'net.ipv4.ip_nonlocal_bind = 1' | sudo tee /etc/sysctl.d/60-nonlocal-bind.conf

The alternative is to bind 0.0.0.0 and filter elsewhere. The sysctl is more precise and keeps the configuration honest about which address the service serves.

What does not move with the VIP

The address moves. These do not:

  • Connection tracking state. Established TCP connections were tracked on the old node. The new node sees mid-stream packets with no matching entry, and a stateful firewall drops them as invalid. Clients see a hang until their TCP timeout rather than a clean reset. conntrackd can replicate state between peers for cases where this matters.
  • TCP connections themselves. Even with conntrack synced, the sockets and their buffers lived in the old kernel. Clients must reconnect. Design the application for it rather than trying to preserve it.
  • Application session state, unless it is in a shared store.
  • The old master’s ARP entries about its peers, which is a smaller problem but occasionally explains an odd first second after failover.

Knowledge check

Knowledge check · 5 questions

  1. Q1. Keepalived has added the VIP to the backup node and ip addr confirms it, but clients still reach the failed node. What is the most likely cause?

  2. Q2. Established TCP connections continue working across a keepalived failover once the VIP has moved.

  3. Q3. Which conditions prevent a gratuitous ARP from correcting the mapping? Select all that apply.

  4. Q4. What does the use_vmac directive change?

  5. Q5. HAProxy on the backup node fails to start because it cannot bind 203.0.113.10, an address the backup does not hold. What is the appropriate fix?

Passing score: 75%. Answers are checked in this browser.