Skip to main content
RunBook Academy

LinuxLVI · Keepalived and VRRPSync groups

Sync groups and multiple VIPs - keeping a service together

Intermediate⏱ ~13 minkeepalivediproute2

What you'll learn

  • Decide between multiple addresses in one instance and multiple instances
  • Configure a vrrp_sync_group and state its all-or-nothing semantics
  • Explain why IPv4 and IPv6 VIPs require separate instances
  • Recognise the failure where half a service fails over

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.

Almost no real service is one address. A load balancer pair fronts an IPv4 VIP and an IPv6 VIP; a database pair has a client address on the application VLAN and a replication address on the storage VLAN. Keepalived will happily let those addresses live on different nodes at the same time, and the result is a service that is up on paper and broken in practice.

Half a service on each node

The failure is worth picturing before the configuration.

lb-01                        lb-02
  VI_FRONTEND  MASTER          VI_FRONTEND  BACKUP
  203.0.113.10                 -
  VI_BACKEND   BACKUP          VI_BACKEND   MASTER
  -                            198.51.100.10

Clients reach lb-01 on the frontend VIP. The backend address they must be proxied to lives on lb-02. Every request fails, and systemctl status keepalived is green on both nodes because each instance is doing exactly what it was told.

This happens whenever two independent VRRP instances make independent decisions - which is what they do by default. A health check that fails on one VLAN, a NIC that goes down, a priority difference, or simply the order the two nodes started in is enough to separate them.

Option one: several addresses, one instance

If the addresses belong to the same interface and the same failure domain, put them in one instance. Members of a single virtual_ipaddress block always move together, because there is only one state machine.

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 150
    advert_int 1
    virtual_ipaddress {
        203.0.113.10/24 dev eth0
        203.0.113.11/24 dev eth0
        203.0.113.12/24 dev eth0
    }
}

This is the correct answer far more often than a sync group is, and it should be the default. One instance means one VRID, one election, one set of advertisements, and no possibility of the addresses separating.

Two constraints to know:

  • Keepalived advertises a bounded number of addresses in the VRRP packet. Beyond that limit, additional addresses go in a virtual_ipaddress_excluded block - they still move with the instance, they are simply not carried in the advertisement. If you are configuring more than a handful of VIPs, check the limit in the documentation for your version rather than discovering it when keepalived rejects the config.
  • All the addresses in one instance share a single state. There is no way to fail one over independently, which is the point and occasionally the problem.

Option two: several instances, one sync group

When the addresses genuinely must be separate instances - a different interface, a different VLAN, or a different address family - a vrrp_sync_group binds their state machines together so they transition as a unit.

vrrp_sync_group VG_SERVICE {
    group {
        VI_FRONTEND
        VI_BACKEND
    }
    notify_master "/etc/keepalived/scripts/promote.sh"
    notify_backup "/etc/keepalived/scripts/demote.sh"
}

vrrp_instance VI_FRONTEND {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 150
    advert_int 1
    virtual_ipaddress {
        203.0.113.10/24 dev eth0
    }
}

vrrp_instance VI_BACKEND {
    state BACKUP
    interface eth1
    virtual_router_id 52
    priority 150
    advert_int 1
    virtual_ipaddress {
        198.51.100.10/24 dev eth1
    }
}

Note the distinct virtual_router_id values. Each instance is a separate VRRP router and needs its own VRID, unique among all VRRP groups on the segment it advertises on. The VRID selects the virtual MAC, so reusing 51 on both would collide with any other pair using the default.

The notify_* scripts belong on the group rather than on the instances. Placed on the group they run once per group transition; placed on the instances they run once per instance, so a two-instance group runs the promotion script twice.

Dual stack needs two instances

A single VRRP instance carries one address family. IPv4 and IPv6 VIPs therefore cannot share an instance, and a dual-stack service always needs at least two instances plus a sync group to keep them together.

vrrp_sync_group VG_WEB {
    group {
        VI_WEB_V4
        VI_WEB_V6
    }
}

vrrp_instance VI_WEB_V4 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 150
    advert_int 1
    virtual_ipaddress {
        203.0.113.10/24 dev eth0
    }
}

vrrp_instance VI_WEB_V6 {
    state BACKUP
    interface eth0
    virtual_router_id 52
    priority 150
    advert_int 1
    virtual_ipaddress {
        2001:db8::10/64 dev eth0
    }
}

Without the sync group, a dual-stack service can end up with IPv4 on one node and IPv6 on the other. Clients with working IPv6 reach one machine and clients without it reach the other - which is an outage that only affects some users, discovered late, and blamed on the application.

The cost of grouping

Interface tracking, and where to put it

The reason instances fault in the first place is usually track_interface or a track_script. Both are configured per instance:

vrrp_instance VI_BACKEND {
    state BACKUP
    interface eth1
    virtual_router_id 52
    priority 150
    advert_int 1
    track_interface {
        eth1
    }
    virtual_ipaddress {
        198.51.100.10/24 dev eth1
    }
}

With the instance in a sync group, eth1 going down now moves the entire service - which is exactly right when the backend path is required to serve, and exactly wrong when it is a replication link the service can briefly do without.

Which of those is true is a decision about the service, not about keepalived. Make it explicitly, and write it in the comment above the track_interface block.

Newer keepalived releases also accept tracking directives at the group level. Check the documentation for your version before relying on that, and verify the parsed result rather than the intent:

sudo keepalived -t -f /etc/keepalived/keepalived.conf
sudo systemctl reload keepalived
journalctl -u keepalived -n 40 --no-pager

The -t flag checks the configuration without running it, which is the difference between finding a typo now and finding it during the next failover.

Verifying the group moves as one

Read-only / Safeboth VIPs on one node - run it on both peers
# ip -br addr show | grep -E '203\.0\.113\.10|198\.51\.100\.10'
eth0             UP             203.0.113.5/24 203.0.113.10/24
eth1             UP             198.51.100.5/24 198.51.100.10/24

Illustrative output

The test is to run it on both nodes during a drill. One node holding both addresses and the other holding neither is a pass. Each node holding one is the split-service failure, and it will not show up in any single-host check.

# Force the group over and confirm it moved as a unit
sudo systemctl stop keepalived        # on the current master
ssh lb-02 "ip -br addr show | grep -E '203\.0\.113\.10|198\.51\.100\.10'"

Knowledge check

Knowledge check · 5 questions

  1. Q1. A pair serves an IPv4 VIP on eth0 and an IPv6 VIP on the same interface. What configuration keeps them on the same node?

  2. Q2. Adding two instances to a sync group is a safe change, because it only constrains behaviour that was previously unconstrained.

  3. Q3. Which situations call for several addresses in a single vrrp_instance rather than a sync group? Select all that apply.

  4. Q4. During a failover drill, lb-01 holds 203.0.113.10 and lb-02 holds 198.51.100.10. Both nodes report keepalived active. What is the state?

  5. Q5. notify_master belongs on the sync group rather than on each instance, because on the group it runs once per group transition.

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