This is a reproducible lab design using example addresses and generic VM labels. VM-01 is 192.168.50.11, VM-02 is 192.168.50.12, and clients connect to 192.168.50.10. Both VMs use ens18 on 192.168.50.0/24. No production identifiers or credentials are needed.

The operational requirement is simple: keep the client address stable while the server behind it changes. I would use this pattern for a redundant reverse proxy or another service that can run independently on either VM. Moving the address does not replicate application data or preserve existing TCP connections; those are separate design decisions.

One address, one elected owner.

A floating IP, or virtual IP (VIP), is an additional address that can move between machines. Each VM keeps its permanent address for administration and peer communication. Only the elected node should add the VIP to its interface. Clients continue using the same destination after failover.

Keepalived is the Linux daemon that manages the address and runs the election through VRRP, the Virtual Router Redundancy Protocol. The active node sends advertisements; a backup takes over when those advertisements stop. This is active-passive availability for one VIP, not load balancing across both VMs.

                    192.168.50.10
                     Floating VIP
                          |
                 +--------+--------+
                 |                 |
          +--------------+  +--------------+
          |    VM-01     |  |    VM-02     |
          |192.168.50.11 |  |192.168.50.12 |
          | MASTER      |  | BACKUP      |
          | Priority 150|  | Priority 100|
          +--------------+  +--------------+
           owns the VIP     no VIP assigned

          Same Layer-2 network: 192.168.50.0/24

Under normal conditions, VM-01 wins with priority 150. If it stops advertising, VM-02 promotes itself, adds the VIP, and announces the new address-to-MAC mapping. When the healthy higher-priority VM returns, default preemption moves ownership back. These guarantees depend on the peers being able to hear one another: VRRP alone cannot enforce exclusive ownership during a partition.

Confirm the network before installing anything.

Start with two Linux VMs, administrative access, static primary addresses, and an unused VIP reserved outside the DHCP allocation pool. Both VMs need the same VLAN or virtual Layer-2 segment. A matching subnet mask is not proof of shared Layer 2: isolated virtual networks can use identical address ranges.

ip -br link
ip -br -4 addr
ip route
ip addr show dev ens18

Expect 192.168.50.11/24 on VM-01 and 192.168.50.12/24 on VM-02. Verify the virtual switch, VLAN, and port-group configuration with the virtualization platform. Then check the path in both directions:

# VM-01
ip route get 192.168.50.12
ping -c 3 192.168.50.12
ip neigh show 192.168.50.12 dev ens18

# VM-02
ip route get 192.168.50.11
ping -c 3 192.168.50.11
ip neigh show 192.168.50.11 dev ens18

For this design, the route should use ens18 directly, without a gateway, and the neighbor entry should resolve. Proxy ARP can make a routed environment look directly connected, so confirm the actual network configuration as well. If ICMP is filtered, use ARP capture and platform evidence instead of interpreting a failed ping as proof of isolation.

The network must permit an additional IP on either VM and accept gratuitous ARP when ownership changes. Check anti-spoofing, allowed-address-pair settings, and virtual switch policies. Some cloud networks require a provider API to move an address; adding it inside the guest is insufficient. Do not statically add the VIP in Netplan, NetworkManager, or both guests: Keepalived must control it.

Install Keepalived on both VMs.

sudo apt update
sudo apt install -y keepalived iputils-arping tcpdump curl
sudo systemctl stop keepalived
keepalived --version

On a RHEL-family distribution, the equivalent package installation is sudo dnf install -y keepalived iputils tcpdump curl. Stop the service there too while preparing the configuration. Package versions differ; use man keepalived.conf for the installed version when an option is rejected.

Before starting either instance, verify the VIP is unallocated in your address inventory and probe for an existing owner from the example segment:

sudo arping -D -I ens18 -c 3 192.168.50.10

Investigate any reply before proceeding. Silence alone does not establish that an address is free: a powered-off machine or a filtering policy can hide a conflict.

Configure VM-01.

Save the following as /etc/keepalived/keepalived.conf on VM-01 using sudoedit /etc/keepalived/keepalived.conf. This is the complete baseline file; it deliberately has no application health check yet.

vrrp_instance VI_SERVICE {
    state BACKUP
    interface ens18
    virtual_router_id 50
    priority 150
    advert_int 1
    version 3

    virtual_ipaddress {
        192.168.50.10/24 dev ens18
    }
}

Configure VM-02.

Save this complete file at the same path on VM-02. The priority is the only difference.

vrrp_instance VI_SERVICE {
    state BACKUP
    interface ens18
    virtual_router_id 50
    priority 100
    advert_int 1
    version 3

    virtual_ipaddress {
        192.168.50.10/24 dev ens18
    }
}

Both files intentionally start with state BACKUP. This is an initial state, not a permanent role. VM-01 still becomes MASTER because it has the higher priority. Starting both as backups lets the election establish ownership and also supports a later decision to delay or disable preemption.

Understand the settings that control ownership.

  • state: initial VRRP state. MASTER is not an instruction to remain master regardless of peers; the election and health state still matter.
  • interface ens18: interface used by this instance. It must exist, be up, and reach the peer on the intended segment.
  • virtual_router_id 50: identifies this virtual router. Both peers must match. Choose a different ID for unrelated VRRP groups sharing the segment; the normal range is 1–255.
  • priority: higher effective priority wins. These examples use 150 and 100. Health tracking can change eligibility or effective priority. Avoid equal priorities; the higher primary IP breaks a tie. Priority 255 is reserved for the address-owner role, not a general “prefer this VM” setting.
  • advert_int 1: the master advertises once per second. This is not a promise of one-second failover.
  • version 3: explicitly selects VRRPv3 on both peers, avoiding reliance on package defaults.
  • virtual_ipaddress: addresses Keepalived adds on promotion and removes on demotion or orderly shutdown. Here the VIP belongs on ens18, with the example subnet prefix.

The legacy authentication block sometimes shown in older examples contains auth_type and auth_pass. It is omitted here: legacy PASS authentication is not part of VRRPv3 and must not be treated as a security boundary. Support and behavior vary by protocol version and implementation. A shared auth_pass does not protect against untrusted network access. Consult the Keepalived configuration reference for version-specific behavior, and restrict VRRP at the network boundary.

With the default advertisement timeout calculation, a priority-100 backup waits roughly 3 × 1 + (256 − 100) / 256 ≈ 3.61 seconds after its last valid advertisement. An orderly stop can announce priority zero and shorten the wait. CPU scheduling, packet loss, ARP convergence, and application readiness all affect the outage observed by a client. The election and timer rules are specified in RFC 9568.

Allow VRRP, then validate and start.

VRRP uses IP protocol number 112, not TCP or UDP port 112. Default IPv4 advertisements go to the well-known multicast address 224.0.0.18, a protocol constant rather than an infrastructure address. Permit advertisements from the two example node addresses on the intended interface in both directions, including any output filtering. Check guest firewalls, hypervisor filtering, and network ACLs. Opening an application port alone does not allow the election to work.

Integrate these requirements into the existing firewall policy; do not flush its rules to test failover. Also allow the client-facing service port to the VIP on either node. ICMP must be allowed for the ping test below, but application traffic needs its own checks.

sudo keepalived --config-test -f /etc/keepalived/keepalived.conf
sudo systemctl enable --now keepalived
systemctl status keepalived --no-pager
sudo journalctl -u keepalived -b --no-pager -n 50

Only enable the service after the configuration test succeeds. The test catches parsing and configuration problems, not firewall or Layer-2 failures. Keepalived documents its command-line options in the program reference.

ip addr
ip addr show dev ens18
ip -o -4 addr show dev ens18
sudo journalctl -u keepalived -f

After election settles, VM-01 should have its permanent address plus 192.168.50.10/24; VM-02 should have only 192.168.50.12/24. Look for MASTER and BACKUP transitions in the logs. Exact log wording varies. A systemd status of “active” does not tell you which node owns the VIP.

Run the failover drill from a third machine.

First confirm that only VM-01 owns the VIP. From another machine on a network that can reach it, leave this running:

ping -D -i 0.2 192.168.50.10

Then stop Keepalived on VM-01:

sudo systemctl stop keepalived

On VM-02, watch the transition and verify the address was added. Also check VM-01 to confirm it removed the VIP:

sudo journalctl -u keepalived --since "2 minutes ago" --no-pager
ip addr show dev ens18

Expected outcome: VM-02 becomes MASTER, 192.168.50.10/24 appears on its interface, and ping resumes after a possible short gap. Record the actual loss rather than assuming the configured timer equals the outage.

sudo systemctl start keepalived

Default preemption lets healthy VM-01, priority 150, reclaim the VIP from VM-02, priority 100. Confirm that VM-02 returns to BACKUP and removes the address. This second move can cause another interruption. If automatic failback is undesirable, nopreempt on both instances keeps an established healthy master in place; retain state BACKUP. That changes the requirement: VM-01 is then preferred at election time, not guaranteed to regain ownership on return.

An orderly service stop is only the first test. In an isolated lab, repeat with a VM power-off and a failed interface while keeping console access. An abrupt failure relies on missed advertisements and can take longer. Test a daemon crash separately: an unclean exit can leave a kernel address behind, and service supervision may restart the daemon. Confirm address cleanup and recovery instead of assuming every failure behaves like systemctl stop.

The VIP also needs a new ARP mapping.

Without a virtual MAC configuration, each VM uses its own interface MAC. Clients on the same subnet—and the final-hop router for routed clients—cache the VIP-to-MAC mapping. After takeover, they may still send frames to the old owner until that mapping changes.

Keepalived sends gratuitous ARP announcements when it becomes master. These tell peers that the VIP is now reachable through the new owner and help update network learning. They are local to the Layer-2 segment; they do not travel through routers. Filtering, static neighbor entries, or ARP suppression can delay or prevent the update.

sudo tcpdump -ni ens18 -e 'ip proto 112 or arp'

If VM-02 owns the VIP but a client cannot reach it, inspect ip neigh show 192.168.50.10 on the device that actually resolves the VIP. For a routed client, that may be the final-hop router. Clearing one stale neighbor entry can help diagnose the problem; routine cache flushing is not a failover design.

Track application readiness, not just a running daemon.

The baseline detects a missing VRRP peer. It does not notice an application that has stopped serving while Keepalived remains healthy. A process-existence check such as pgrep can detect a missing process, but a running process may be deadlocked, unable to reach storage, or returning errors. Prefer a bounded readiness request that represents whether this node can serve traffic.

For this optional example, the application on each VM exposes /ready on its permanent address, port 8080, and returns exactly HTTP 200 when ready. Use the local node address, never the VIP: probing the VIP from the backup could accidentally test the other node. Both applications must be able to run without owning the VIP; bind appropriately, for example to all intended local addresses, and restrict access with the firewall.

Create /usr/local/bin/check-vip-service on VM-01 with the following content. On VM-02, use the same script with 192.168.50.12 in the URL. The executable and its parent directories must be protected against writes by unprivileged accounts.

#!/bin/sh
status=$(/usr/bin/curl --silent --show-error --noproxy '*' \
    --connect-timeout 1 --max-time 2 \
    --output /dev/null --write-out '%{http_code}' \
    http://192.168.50.11:8080/ready) || exit 1
[ "$status" = "200" ]
sudo chown root:root /usr/local/bin/check-vip-service
sudo chmod 0755 /usr/local/bin/check-vip-service
sudo -u nobody /usr/local/bin/check-vip-service
echo $?

Here root and nobody are standard OS accounts, not environment-specific identities. The last command should print zero for a ready service. Use a dedicated unprivileged service account in production and verify its access; this example uses nobody for a credential-free HTTP request. Check SELinux or AppArmor policy if execution is denied.

On both nodes, add these top-level blocks before the existing vrrp_instance. There should be one global block, so merge settings if a file already has one:

global_defs {
    enable_script_security
}

vrrp_script check_service {
    script "/usr/local/bin/check-vip-service"
    user nobody
    interval 3
    timeout 2
    fall 2
    rise 2
    weight 0
    init_fail
}

Add this block inside vrrp_instance VI_SERVICE on each VM:

track_script {
    check_service
}

A zero weight makes failed health tracking put the instance into FAULT, removing its eligibility and VIP. Two consecutive failures trigger that decision; two successes permit recovery. init_fail requires health to be established at startup. This policy allows neither unhealthy node to advertise the service: if both fail their checks, neither should own the VIP. Confirm these tracking semantics against the installed-version configuration reference.

For a soft preference instead, replace weight 0 with weight -60. An unhealthy VM-01 then falls from 150 to 90, below a healthy VM-02 at 100. A penalty of only 20 would leave VM-01 at 130 and would not produce the intended takeover. Weighted tracking depends on preemption: do not combine it with nopreempt and expect a healthy backup to displace an unhealthy, still-advertising master.

The trade-off is explicit. With a negative weight, an unhealthy node remains eligible, so one can still own the VIP if both applications fail. Use the zero-weight FAULT policy when serving from an unhealthy node is unacceptable. Avoid a readiness check that makes both nodes flap because of one shared dependency unless withdrawing the entire service is the intended response.

sudo keepalived --config-test -f /etc/keepalived/keepalived.conf
sudo systemctl reload keepalived
sudo journalctl -u keepalived --since "2 minutes ago" --no-pager

Reload only after validation passes. In the lab, make VM-01’s readiness endpoint return a non-200 status while keeping Keepalived running. Verify takeover, then restore readiness and observe failback. Test both endpoints failing as well. Measure service requests through http://192.168.50.10:8080/ready from the third machine; ping alone cannot prove application recovery. The probe detects local readiness, so an external service monitor is still necessary.

Use unicast when multicast is unavailable.

If the platform blocks VRRP multicast but supports the shared VIP and ARP movement, use Keepalived’s unicast peer mode. Keep the baseline settings and add the following inside VM-01’s instance:

unicast_src_ip 192.168.50.11
unicast_peer {
    192.168.50.12
}

On VM-02, reverse the source and peer:

unicast_src_ip 192.168.50.12
unicast_peer {
    192.168.50.11
}

Configure both ends consistently, validate, and reload. Permit IP protocol 112 between the permanent addresses in both directions. Unicast changes advertisement delivery; it neither authenticates peers nor makes a guest-managed VIP work on a network that forbids address movement. This article still assumes a common Layer-2 service segment.

Troubleshoot ownership before changing timers.

  • Both nodes say MASTER: capture protocol 112 on both interfaces. Check bidirectional firewall rules, multicast support or reciprocal unicast peers, matching VRRP version, and matching instance settings. Ping between primary addresses does not prove advertisements pass. Treat duplicate VIP ownership as an incident: use console access to stop or isolate one owner while restoring peer communication.
  • Neither node owns the VIP: inspect the configuration-test output, interface link state, and logs for FAULT or script failures. Run the readiness probe as its configured account. Check permissions and mandatory-access-control denials.
  • Wrong interface: compare ip -br addr with the config. An interface name copied from another VM may exist but connect to the wrong network.
  • Unexpected elections: check for another group using virtual_router_id 50 on the same segment. The ID must match within this pair and be distinct from unrelated groups.
  • VIP moves but clients fail: inspect gratuitous ARP, neighbor caches, VIP anti-spoofing rules, and application binding. Check ss -lntp to confirm the expected listener; verify service-port firewall rules on both VMs.
  • Only one direction works: inspect output ACLs and virtual switch filters as well as guest input rules. Capture at both ends to distinguish “sent” from “received.”
  • Frequent transitions: inspect timestamped Keepalived logs alongside packet loss, host CPU contention, VM pauses, and probe latency. Do not reduce advertisement timers to hide an unstable network.
sudo journalctl -u keepalived -b --no-pager -n 100
ip -br -4 addr show dev ens18
ip route
sudo ss -lntp
sudo tcpdump -ni ens18 -e 'ip proto 112 or arp'

Define the production failure contract.

A two-node VRRP pair has no quorum or fencing. If advertisements are partitioned while both nodes remain connected to clients, each can consider itself master. The design cannot promise “only one owner at all times” under that failure. For stateful writers, use an application cluster with appropriate consensus or fencing; moving a VIP is not a substitute. The VRRP specification describes the protocol’s scope and security considerations.

Place the VMs in independent failure domains where possible, while preserving the required network connectivity. Keep application configuration and certificates synchronized through a separate deployment process, and design data replication independently. A failed hypervisor, shared switch, or shared storage system can still take down both nodes.

Before production, record the behavior for orderly stops, abrupt VM failure, application failure, both applications unhealthy, peer-network partition, and recovery of the preferred node. Observe from a real client path and count failed requests as well as missed pings. Existing connections may reset, so clients need sensible reconnection behavior. Alert on dual ownership, no owner, FAULT state, and repeated transitions.

The useful outcome is a repeatable handover: one eligible owner, a reachable service, and evidence showing what happened when either failed. A green Keepalived service status is only the start of that verification.