Skip to main content
RunBook Academy

← All runbooks in Proxmox VE

critical riskcluster affecting~480 min

Upgrade a cluster from PVE 8 to PVE 9, node by node

1 · Prerequisites

Confirm every item is in place before any state change.

  • Every node is already on the latest Proxmox VE 8.4 point release, with no pending updates
  • A verified, restore-tested backup exists for every guest, and the restore test was done from the PBS this cluster actually uses
  • Out-of-band console access (iDRAC / iLO / IPMI) is confirmed working on every node, tested today, not assumed
  • If Ceph is in use, it is already running Squid (19.2.x) before any node is upgraded
  • A maintenance window long enough for the slowest node plus one full rollback of that node
  • The upgrade has been rehearsed end to end on a non-production cluster of the same shape
  • A named person who is authorised to stop the upgrade after any node

2 · Pre-checks

Read-only diagnostic commands. If any of these don't match expected output, stop and investigate further.

  • · pveversion -v shows the same 8.4 package set on every node
  • · pvecm status reports Quorate: Yes with expected votes equal to the node count
  • · pve8to9 --full runs clean on every node: zero FAIL and every WARN individually explained
  • · ceph -s reports HEALTH_OK and ceph versions shows a single Squid version across mon, mgr and osd
  • · ha-manager status shows no service in error or fence state
  • · df -h / and df -h /var/lib/vz show enough free space for the download and unpack of a full release upgrade
  • · Every guest that must not be interrupted is identified, and its migration target is chosen in advance

3 · Procedure

Execute each step in order. Verify the expected output of a step before moving to the next.

  1. 1Freeze change: stop backup jobs, replication jobs and any automation that writes to the cluster
  2. 2On every node, run pve8to9 --full and resolve every FAIL; record a written justification for every remaining WARN
  3. 3Pick the first node to upgrade: the one carrying the least critical workload, never the one holding the only copy of anything
  4. 4Put that node into HA maintenance mode so HA moves its services rather than fencing it
  5. 5Migrate or shut down every guest on that node and confirm the node is empty
  6. 6Take a copy of the node repository configuration and the guest configs it owns
  7. 7Switch the node repositories from Bookworm to Trixie, including the Ceph and Proxmox repositories
  8. 8Run apt update, review the transaction with apt dist-upgrade before confirming, then run it
  9. 9Answer configuration-file prompts deliberately; keep the local version only where you know why it differs
  10. 10Reboot the node and watch it come back on the console, not on ping
  11. 11Verify the upgraded node fully before touching the next one: version, quorum, storage, Ceph daemons, HA
  12. 12Take the node out of HA maintenance mode and return a small, low-value guest to it first
  13. 13Stop and reassess if anything is unexplained; only then move to the next node
  14. 14After the last node, upgrade the cluster-wide items that were deferred and clear the mixed-version state

4 · Verification

Confirm the procedure actually fixed the problem.

  • pveversion -v on every node reports pve-manager 9.x and the same package set
  • cat /etc/debian_version on every node reports a Trixie release, and no node still reports Bookworm
  • pvecm status is quorate with expected votes equal to the node count, on every node
  • ceph versions reports a single release for mon, mgr, osd and mds, and ceph -s is HEALTH_OK with all PGs active+clean
  • ha-manager status lists every HA service as started on a known node, none in error
  • apt list --upgradable is empty on every node
  • Every guest that was running before the window is running now, and each one has been opened and checked at the application level, not merely reported as running
  • A live migration succeeds in both directions between two upgraded nodes
  • A test restore from PBS succeeds against the upgraded cluster

5 · Rollback

If verification fails, undo the procedure in reverse order.

  • There is no supported downgrade from PVE 9 to PVE 8. Once apt dist-upgrade begins on a node, that node is committed
  • Before the dist-upgrade on a node, rollback is trivial: revert the repository files, apt update, and the node is unchanged
  • After the dist-upgrade, the only rollback for that node is a reinstall of PVE 8.4 followed by a rejoin - which means the replace-failed-node-same-name procedure, and hours, not minutes
  • If a node fails to come back, do NOT upgrade another node. A cluster with one broken node and the rest on 8.4 is recoverable; a cluster with three half-upgraded nodes is an outage
  • A mixed 8/9 cluster is supported only for the duration of the upgrade. Live migration from a 9 node back to an 8 node is not supported, so an upgraded node is a one-way destination for running guests
  • If the upgrade is abandoned mid-way, the recovery position is: finish the upgrade of the already-upgraded nodes and consolidate guests there, or rebuild those nodes at 8.4. Sitting in mixed mode is not a resting state

6 · Escalation

When the runbook isn't enough, contact:

  • · Escalate to the cluster owner before the first dist-upgrade, and again before each subsequent node if any WARN was accepted rather than fixed
  • · Escalate immediately if a node does not come back from the reboot - that needs console access and a decision, not a second reboot
  • · Escalate to the storage owner if Ceph reports anything other than HEALTH_OK at any point; do not upgrade another node while Ceph is degraded
  • · Escalate if quorum is lost at any moment during the window. Do not attempt to force it
  • · Escalate to Proxmox support if pve8to9 reports a condition the upgrade guide does not cover

Verified against Proxmox VE 9.2.4 upgrading from 8.4, with Ceph Squid and Proxmox Backup Server 4.2.5.

A major PVE upgrade is not a patch. It moves the node across a Debian release boundary, replaces the kernel, QEMU, corosync and Ceph packages, and rewrites parts of the on-disk configuration. The cluster survives it because you do one node at a time and stop at the first thing you cannot explain - not because the upgrade is safe.

The single most useful framing: each node has exactly one moment where rollback stops being cheap. Before apt dist-upgrade runs, reverting is two file edits. After it, the node is a reinstall. Everything in this runbook exists to make sure you arrive at that moment having already answered every question.

When to use this runbook

  • Proxmox VE 8.4 is approaching or past end of support.
  • A feature, driver or hardware platform requires PVE 9.
  • The rest of the estate has moved and this cluster is the outlier.

Do not use it to upgrade a cluster that is currently unhealthy. An upgrade is not a repair, and a degraded Ceph cluster or a flapping corosync link becomes considerably harder to diagnose once the versions differ between nodes.

Blast radius

One node at a time, and the guests on it. But the honest blast radius is larger than that, because a mixed-version cluster has properties the homogeneous one did not:

during the window     guests can migrate 8 -> 9, but NOT 9 -> 8
                      an upgraded node is a one-way destination
                      corosync speaks across versions, but do not linger
                      Ceph daemons must not be mid-upgrade on two nodes at once
after the last node   normal again, once the deferred cluster-wide steps are done

Write the node order down before you start, and the guest placement for each step. Improvising guest placement during a mixed-version window is how a critical VM ends up on a node that cannot hand it back.

Step 1: Get to the starting line

The upgrade guide assumes a fully updated 8.4. That is not a formality - the 8.4 point releases contain the compatibility shims the upgrade path depends on.

Read-only / Safeevery node, same versions
# Run on each node and compare the output side by side
pveversion -v
cat /etc/debian_version
pvecm status
ha-manager status
Configuration changebring 8.4 fully current first
apt update
apt dist-upgrade
pveversion
# Reboot if a new kernel was installed; do that node-by-node too.

A node that is on a different 8.4 point release from its peers is a variable you do not want in the upgrade. Level them first.

Step 2: Run the checker, and treat WARN as work

Read-only / Safepve8to9 on every node
pve8to9 --full 2>&1 | tee "/root/pve8to9-$(hostname -s).txt"
grep -cE '^FAIL' "/root/pve8to9-$(hostname -s).txt"
grep -E '^(WARN|FAIL)' "/root/pve8to9-$(hostname -s).txt"

Zero FAIL is the bar to proceed. WARN is the interesting category: the checker cannot decide for you, so each one needs a person to read it and write down why it is acceptable. The ones that most often matter:

FindingWhat it usually meansWhat to do
Ceph is not on SquidThe cluster runs Quincy or ReefUpgrade Ceph fully before any node upgrade. Do not combine them
Non-default /etc/apt entriesA third-party repositoryEstablish whether it has a Trixie suite. If not, remove it for the window
Guests with unusual configurationPassthrough, custom args, old machine typesThese are the guests that will not start after the upgrade. Test them first
Free space on / below thresholdThe dist-upgrade needs headroomClear it. A dist-upgrade that runs out of space mid-transaction is the worst case
systemd units in failed stateSomething is already brokenFix it now. Do not carry a failure across a release boundary
Old kernel still bootedThe node has not rebooted since its last updateReboot into the current 8.4 kernel first, so you know it boots

Step 3: Empty the node, properly

Service impact possibledeclare maintenance and evacuate
NODE=pve02

# HA moves services away instead of treating the reboot as a failure
ha-manager crm-command node-maintenance enable "$NODE"
sleep 30
ha-manager status

# Anything left behind?
pvesh get /nodes/"$NODE"/qemu --output-format json | head -40
pvesh get /nodes/"$NODE"/lxc  --output-format json | head -40

Migrate what can migrate, shut down what cannot. A guest with PCI passthrough, a host CD-ROM or a local-only disk will not live-migrate; decide its fate before the window rather than at 02:40.

Read-only / Safeprove the node is empty
NODE=pve02
RUNNING=$(pvesh get /cluster/resources --type vm --output-format json \
| grep -c "\"node\":\"$NODE\"")
echo "guests still assigned to $NODE: $RUNNING"

Step 4: Keep what you will want if this goes wrong

Read-only / Safecopy before you change
NODE=$(hostname -s)
STAMP=$(date +%Y%m%d-%H%M)
mkdir -p "/root/pre-upgrade-$STAMP"
cp -a /etc/apt/sources.list /etc/apt/sources.list.d "/root/pre-upgrade-$STAMP/"
cp -a /etc/network/interfaces "/root/pre-upgrade-$STAMP/"
cp -a /etc/pve/nodes/"$NODE" "/root/pre-upgrade-$STAMP/pve-node-config"
pveversion -v > "/root/pre-upgrade-$STAMP/pveversion.txt"
ls -la "/root/pre-upgrade-$STAMP"

Note that /root is local to this node. If the node is unrecoverable the copy goes with it - so also put the pve8to9 output and the version list somewhere off the node.

Step 5: Move the repositories to Trixie

Configuration changewhat points where right now
grep -rhE '^(deb|Suites:|URIs:)' /etc/apt/sources.list /etc/apt/sources.list.d/ \
| sort -u

Every Proxmox-related and Debian-related suite moves from bookworm to trixie: the Debian base, pve-no-subscription or pve-enterprise, and the Ceph repository. PVE 9 uses the deb822 .sources format; the upgrade guide’s conversion is the authoritative sequence and it is short. Follow it rather than hand-editing, and then read the result back:

Read-only / Safenothing left on bookworm
apt update
grep -rl bookworm /etc/apt/sources.list /etc/apt/sources.list.d/ || echo 'no bookworm references remain'
apt policy | head -30

A single forgotten third-party repository still pointing at Bookworm will either fail apt update or, worse, succeed and pull a package built against the old libraries. Resolve it before continuing.

Step 6: The dist-upgrade

Service impact possibleread the transaction before confirming it
apt update
apt dist-upgrade
# Read the summary. Look specifically for packages being REMOVED.
# A removal of a pve-* or ceph-* package is a stop condition, not a detail.

Two things demand attention while it runs:

Configuration file prompts. apt will ask about files you or your automation modified. The default - keep the local version - is usually wrong for Proxmox-managed files and usually right for files you deliberately customised. If you cannot say which category a file is in, choose to keep the local version and note it, because a kept file is recoverable and an overwritten one is not.

Anything about /etc/network/interfaces. Answer conservatively. A node that boots with the wrong network configuration is a console recovery, and the copy from Step 4 is what you will restore from.

Step 7: Reboot on the console

Service impact possiblereboot, watching the console
sync
systemctl reboot

Watch it on the out-of-band console. Ping coming back proves the NIC is up; it does not prove the root filesystem mounted read-write, that pve-cluster started, or that the ZFS pool imported. The console shows you a failure two minutes before SSH would have.

Step 8: Verify this node before touching the next

Read-only / Safethe node itself
pveversion -v
cat /etc/debian_version
uname -r
systemctl --failed --no-pager
journalctl -b -p err --no-pager | tail -40
Read-only / Safethe cluster's view of it
pvecm status
pvecm nodes
corosync-cfgtool -s
pvesm status
ha-manager status
Read-only / Safeceph, if present
ceph -s
ceph versions
ceph osd tree
ceph health detail

ceph versions during the window will legitimately show two releases - that is the mixed state. What it must never show is a daemon that failed to start. ceph -s must be HEALTH_OK or a warning you have read and understood before the next node is touched.

Step 9: Return workload gradually

Service impact possibleone small guest first
NODE=pve02
VMID=9001

ha-manager crm-command node-maintenance disable "$NODE"
qm migrate "$VMID" "$NODE" --online
sleep 20
pvesh get /nodes/"$NODE"/qemu/"$VMID"/status/current --output-format yaml

Move one guest you can afford to break, confirm it runs and serves, and only then return the rest. The first guest on a freshly upgraded node is a test, and it should be chosen as one.

Step 10: Repeat, then close the mixed state

Work through the remaining nodes in the recorded order. After the last one:

Read-only / Safecluster-wide close-out
for N in $(pvecm nodes | awk '/^ *[0-9]/ {print $3}'); do
echo "== $N"
ssh "$N" 'pveversion; cat /etc/debian_version; apt list --upgradable 2>/dev/null | tail -n +2 | wc -l'
done

Then re-enable what you froze in Step 1 - backup jobs, replication jobs, automation - and run one of each by hand before trusting the schedule.

Read-only / Safeprove the cluster still does its job
VMID=9001
SRC=pve01
DST=pve02

qm migrate "$VMID" "$DST" --online && sleep 15 && qm migrate "$VMID" "$SRC" --online
vzdump "$VMID" --storage pbs-main --mode snapshot --notes-template 'post-upgrade smoke test'

Migration in both directions and one successful backup is the smallest proof that the cluster is whole. A cluster where every node reports 9.x but live migration fails is not upgraded, it is broken quietly.

Rollback in practice

Where you areWhat rollback means
Before repository changeNothing to undo
Repositories changed, dist-upgrade not runRestore /etc/apt from the Step 4 copy, apt update, done
dist-upgrade runningDo not interrupt it. If it failed, dpkg --configure -a and apt -f install on the console before any reboot
dist-upgrade completeNo downgrade exists. Forward to a working 9.x, or reinstall and rejoin
Node upgraded, guests unhappyMigrate guests back to a remaining 8.4 node only if they were never started on 9 - a 9 to 8 live migration is unsupported. Otherwise shut down, migrate offline, and start on 8.4
Two or more nodes upgraded, cluster unstableStop. Consolidate guests onto the healthiest set of nodes, then decide: finish forward, or rebuild the upgraded nodes at 8.4

Common patterns

SymptomLikely causeResolution
apt update fails after the repository switchA third-party repository with no Trixie suiteDisable it for the window; revisit after
Node boots but /etc/pve is emptypve-cluster did not startsystemctl status pve-cluster, then the pmxcfs recovery runbook
Guest will not start after the upgradeOld machine type, removed device model, or passthrough IDs changedqm showcmd VMID --pretty; see the VM start-failure runbook
Live migration fails between an upgraded and a non-upgraded nodeDirection is 9 to 8Not supported. Migrate offline, or finish the upgrade
Ceph OSD does not come up after rebootCeph release was not Squid before the upgradeStop. Fix Ceph on the remaining 8.4 nodes first
Network unreachable after rebootAn interfaces file was overwritten or the NIC name changedConsole, restore from the Step 4 copy, ifreload -a
pvecm status shows the node but the GUI shows it greyCertificates not regenerated for the new statepvecm updatecerts --force, then restart pveproxy

Escalation

Escalate when:

  • A node does not return from the reboot.
  • Ceph reports anything other than HEALTH_OK between nodes.
  • Quorum is lost at any point.
  • A pve8to9 WARN was accepted and the thing it warned about has now happened.
  • The estate is stuck in mixed-version state at the end of the window.

References

  1. Proxmox VE - Upgrade from 8 to 9
  2. Proxmox VE - Package repositories
  3. Proxmox VE - Cluster manager
  4. Ceph Squid release notes