Skip to main content
RunBook Academy

Proxmox VEXII · High AvailabilityHA advanced

Anatomy of a bad fence: two post-mortems

Advanced⏱ ~28 minha-managercorosync-cfgtool

What you'll learn

  • Reconstruct a fencing event from pve-ha-crm, pve-ha-lrm and corosync logs after the fact
  • Distinguish a correct fence, a false-positive fence and a fence that failed to happen
  • Explain why disabling fencing after a false positive makes the next incident unrecoverable
  • Name the design changes that reduce spurious fencing without weakening the guarantee

Prerequisites

Verified against Proxmox VE 9.2.4 · Proxmox Backup Server 4.2.5 · Ceph Squid / Tentacle · Debian 13 (Trixie) · Linux kernel 7.0 (PVE 9.2 default) · 2026-08-12

Not yet marked complete on this device.

Fencing is the least-loved part of a cluster. It is the mechanism that reboots your servers, and the only evidence it worked is an absence: the corruption that did not happen.

So the first fencing event most operators experience is one they consider a malfunction, and the reflex it produces - “turn that off” - is the single most dangerous response available. This lesson works through two incidents to explain why.

Both are composites of common real failures, with logs reconstructed to show what the evidence actually looks like. The mechanism they demonstrate is described in the Linux course at why fencing exists and STONITH and data integrity; this lesson is the Proxmox-specific forensics.

Where the evidence lives

Three logs, on different machines, telling different parts of the story.

SourceRuns onTells you
journalctl -u pve-ha-crmThe master nodeWhat the cluster decided and why
journalctl -u pve-ha-lrmEvery nodeWhat each node was asked to do and whether it complied
journalctl -u corosyncEvery nodeMembership changes - the input to every decision
journalctl -k on the fenced nodeThe victim, after rebootWhether the watchdog fired, and whether anything preceded it
Read-only / Safecollect the evidence before it rotates
set -euo pipefail
SINCE='2026-08-11 03:00'
UNTIL='2026-08-11 04:00'
OUT=/root/fence-postmortem-$(date +%F)
mkdir -p "$OUT"

for NODE in pve-01 pve-02 pve-03; do
for UNIT in pve-ha-crm pve-ha-lrm corosync; do
  ssh -o BatchMode=yes "root@$NODE" \
    "journalctl -u $UNIT --since '$SINCE' --until '$UNTIL' --no-pager" \
    > "$OUT/$NODE-$UNIT.log" 2>/dev/null || true
done
ssh -o BatchMode=yes "root@$NODE" \
  "journalctl -k --since '$SINCE' --until '$UNTIL' --no-pager" \
  > "$OUT/$NODE-kernel.log" 2>/dev/null || true
done

ls -la "$OUT"

The fenced node’s own journal is the piece most often lost. It rebooted; if the journal was not persistent, everything it knew about its final minute is gone. Making the journal persistent on cluster nodes - Storage=persistent in journald.conf - costs a little disk and is the difference between a post-mortem and a guess.

Post-mortem 1: the node that was fenced for being busy

What was observed. At 03:41, pve-03 rebooted. Four VMs restarted on other nodes. pve-03 came back healthy in three minutes, with no hardware errors, no kernel panic and clean IPMI logs. Monitoring showed the node had been responsive to ICMP throughout.

What the CRM master recorded:

Read-only / Safepve-02 (CRM master): the decision
# journalctl -u pve-ha-crm --since '03:38' --until '03:45' --no-pager
03:39:51 pve-ha-crm[2411]: node 'pve-03': state changed from 'online' => 'unknown'
03:40:51 pve-ha-crm[2411]: service 'vm:140': state changed from 'started' to 'fence'
03:40:51 pve-ha-crm[2411]: service 'vm:141': state changed from 'started' to 'fence'
03:41:31 pve-ha-crm[2411]: node 'pve-03': state changed from 'unknown' => 'fence'
03:42:11 pve-ha-crm[2411]: successfully acquired lock 'ha_agent_pve-03_lock'
03:42:11 pve-ha-crm[2411]: fencing: acknowledged - got agent lock for node 'pve-03'
03:42:11 pve-ha-crm[2411]: node 'pve-03': state changed from 'fence' => 'unknown'
03:42:11 pve-ha-crm[2411]: service 'vm:140': state changed from 'fence' to 'recovery'
03:42:12 pve-ha-crm[2411]: recover service 'vm:140' from fenced node 'pve-03' to node 'pve-01'

Illustrative output

What corosync recorded on a surviving node:

Read-only / Safepve-01: membership, which is the real input
# journalctl -u corosync --since '03:38' --until '03:45' --no-pager
03:39:47 corosync[1802]: [KNET  ] link: host: 3 link: 0 is down
03:39:47 corosync[1802]: [KNET  ] host: host: 3 has no active links
03:39:49 corosync[1802]: [TOTEM ] Token has not been received in 3225 ms
03:39:51 corosync[1802]: [TOTEM ] A processor failed, forming new configuration
03:39:53 corosync[1802]: [QUORUM] Members[2]: 1 2
03:42:47 corosync[1802]: [KNET  ] rx: host: 3 link: 0 is up
03:43:02 corosync[1802]: [QUORUM] Members[3]: 1 2 3

Illustrative output

What the victim’s kernel log showed after reboot:

Read-only / Safepve-03: the last thing it knew
# journalctl -k -b -1 --since '03:38' --no-pager | tail -6
03:39:12 pve-03 kernel: bond0: (slave eno1): link status down, disabling slave
03:39:12 pve-03 kernel: bond0: now running without any active interface!
03:39:12 pve-03 kernel: vmbr0: port 1(bond0) entered disabled state
03:39:45 pve-03 kernel: bond0: (slave eno2): link status up, enabling slave
03:39:45 pve-03 kernel: bond0: now running without any active interface!
03:41:58 pve-03 kernel: watchdog: watchdog0: watchdog did not stop!

Illustrative output

The reconstruction. A switch firmware update took a link down. The bond had both members on the same switch, so the bond had no active interface for 33 seconds. Corosync lost the node at 03:39:47; the token timeout expired at 03:39:49; the cluster reformed without it. pve-03 had lost quorum, so its LRM could not renew its agent lock, so its watchdog was no longer being reset, and at 03:41:58 the watchdog fired.

Was this a malfunction? No. Every component did exactly what it was designed to do. The node genuinely could not communicate with the cluster, and a node that cannot communicate cannot be trusted not to be running guests.

Was the design correct? No. The failure was a single switch taking out every corosync path simultaneously, which is a design defect, not a fencing defect.

The design changes that actually apply:

  1. Corosync links on physically separate paths. Not two ports in one bond to one switch - a second ring on a different switch, ideally a different NIC. Corosync supports up to eight links and handles the failover itself; this is what corosync link tuning covers, and it is the single highest-value change on this list.
  2. Corosync on a network that backups and migrations do not share. Token loss under load is the other common false-positive source.
  3. Node maintenance mode before switch work. If the switch update had been preceded by ha-manager crm-command node-maintenance enable pve-03, the guests would have migrated off first and the isolation would have cost nothing.
Cluster-wide riskverify every corosync link before touching network hardware
set -euo pipefail

corosync-cfgtool -s

# The same question from the cluster's own view.
pvecm status | sed -n '/Membership information/,$p'

# Which physical interfaces carry those links.
grep -E 'ring[0-9]+_addr|linknumber' /etc/pve/corosync.conf

Post-mortem 2: the fence that nearly did not happen

What was observed. At 21:14, pve-01 stopped responding. Guests did not recover for eleven minutes. When they finally did, an operator had already logged into pve-01’s IPMI and found the node powered on with a responsive console.

What the CRM recorded:

Read-only / Safepve-02 (CRM master): the wait
# journalctl -u pve-ha-crm --since '21:14' --until '21:30' --no-pager
21:14:22 pve-ha-crm[2088]: node 'pve-01': state changed from 'online' => 'unknown'
21:15:22 pve-ha-crm[2088]: service 'vm:100': state changed from 'started' to 'fence'
21:16:02 pve-ha-crm[2088]: node 'pve-01': state changed from 'unknown' => 'fence'
21:16:02 pve-ha-crm[2088]: fencing: waiting for agent lock 'ha_agent_pve-01_lock'
21:17:02 pve-ha-crm[2088]: fencing: waiting for agent lock 'ha_agent_pve-01_lock'
21:20:02 pve-ha-crm[2088]: fencing: waiting for agent lock 'ha_agent_pve-01_lock'
21:25:14 pve-ha-crm[2088]: successfully acquired lock 'ha_agent_pve-01_lock'
21:25:14 pve-ha-crm[2088]: fencing: acknowledged - got agent lock for node 'pve-01'
21:25:15 pve-ha-crm[2088]: recover service 'vm:100' from fenced node 'pve-01' to node 'pve-03'

Illustrative output

What pve-01 looked like after recovery:

Read-only / Safepve-01: what was configured, and what was not
# systemctl status watchdog-mux --no-pager | head -5
ls -l /dev/watchdog*
cat /etc/default/pve-ha-manager
● watchdog-mux.service - Proxmox VE watchdog multiplexer
   Loaded: loaded (/lib/systemd/system/watchdog-mux.service; enabled)
   Active: active (running) since Mon 2026-08-10 09:12:44 CEST
ls: cannot access '/dev/watchdog*': No such file or directory
WATCHDOG_MODULE=ipmi_watchdog

Illustrative output

The reconstruction. WATCHDOG_MODULE=ipmi_watchdog was set during build from a runbook written for a different server generation. On this hardware the module name is different, so it never loaded, so /dev/watchdog never existed

  • and watchdog-mux started successfully anyway, reporting active (running) in every health check anyone had written.

The node had a storage-driver hang. Its kernel was alive enough to hold the agent lock’s lease alive intermittently but not enough to do useful work. With no watchdog, nothing reset it. The CRM waited eleven minutes for a lock that was only released when the hang cleared on its own.

Why this is the more dangerous incident. The first post-mortem was a correct fence for a bad reason and cost three minutes. This one was an eleven-minute unavailability with no automatic recovery, and it was one scheduler difference away from being worse: had the wedged node resumed executing while still believing it owned the guests, and had the CRM already started them elsewhere, both would have been writing to the same images.

Fence or not: reading the three cases apart

EvidenceDiagnosis
Corosync link down, node’s kernel log ends at watchdog, node healthy on returnFalse-positive fence. Fix the network path, not the fencing.
Node fenced, hardware or kernel error in IPMI or the pre-reboot journalCorrect fence. The mechanism did its job; fix the fault.
CRM logs waiting for agent lock repeatedly, recovery delayed by minutesFencing did not happen. Check for a watchdog device on the victim.
Guests recovered but the original node was never rebootedInvestigate immediately. Verify no guest ran in two places.
Node fenced during a backup or migration window, repeatedlyCorosync sharing a saturated network. Separate it.

The post-mortem write-up

A fencing post-mortem that does not answer these five questions has not finished:

  1. What did corosync see, and when? Membership is the input to everything else. If corosync did not lose the node, the incident is not a fencing incident.
  2. Was the node genuinely unable to participate? Fenced-and-healthy means a network defect. Fenced-and-broken means the mechanism worked.
  3. Did the watchdog fire, or did the CRM wait? These are opposite failure modes with opposite fixes, and the CRM log distinguishes them in one line.
  4. Could any guest have run in two places? If the answer is not a confident no, treat the affected images as suspect and verify them.
  5. What would have prevented it without weakening the guarantee? If the proposed remedy is a longer timeout or less fencing, the analysis is not finished.

Knowledge check

Knowledge check · 5 questions

  1. Q1. The CRM log shows "fencing: waiting for agent lock" repeated for eleven minutes before recovery finally proceeds. What does this indicate?

  2. Q2. A node was fenced after a switch firmware update took its bond down for 33 seconds. It returned healthy with no hardware faults. What is the correct remedy?

  3. Q3. Which of these are true about disabling fencing after a false-positive fence? Select all that apply.

  4. Q4. watchdog-mux can report "active (running)" on a node that has no watchdog device at all.

  5. Q5. Which ha-manager command takes a node out of HA placement for planned maintenance without disarming fencing anywhere?

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