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.
- 1Freeze change: stop backup jobs, replication jobs and any automation that writes to the cluster
- 2On every node, run pve8to9 --full and resolve every FAIL; record a written justification for every remaining WARN
- 3Pick the first node to upgrade: the one carrying the least critical workload, never the one holding the only copy of anything
- 4Put that node into HA maintenance mode so HA moves its services rather than fencing it
- 5Migrate or shut down every guest on that node and confirm the node is empty
- 6Take a copy of the node repository configuration and the guest configs it owns
- 7Switch the node repositories from Bookworm to Trixie, including the Ceph and Proxmox repositories
- 8Run apt update, review the transaction with apt dist-upgrade before confirming, then run it
- 9Answer configuration-file prompts deliberately; keep the local version only where you know why it differs
- 10Reboot the node and watch it come back on the console, not on ping
- 11Verify the upgraded node fully before touching the next one: version, quorum, storage, Ceph daemons, HA
- 12Take the node out of HA maintenance mode and return a small, low-value guest to it first
- 13Stop and reassess if anything is unexplained; only then move to the next node
- 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.
# Run on each node and compare the output side by side
pveversion -v
cat /etc/debian_version
pvecm status
ha-manager statusapt 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
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:
| Finding | What it usually means | What to do |
|---|---|---|
| Ceph is not on Squid | The cluster runs Quincy or Reef | Upgrade Ceph fully before any node upgrade. Do not combine them |
Non-default /etc/apt entries | A third-party repository | Establish whether it has a Trixie suite. If not, remove it for the window |
| Guests with unusual configuration | Passthrough, custom args, old machine types | These are the guests that will not start after the upgrade. Test them first |
Free space on / below threshold | The dist-upgrade needs headroom | Clear it. A dist-upgrade that runs out of space mid-transaction is the worst case |
| systemd units in failed state | Something is already broken | Fix it now. Do not carry a failure across a release boundary |
| Old kernel still booted | The node has not rebooted since its last update | Reboot into the current 8.4 kernel first, so you know it boots |
Step 3: Empty the node, properly
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 -40Migrate 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.
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
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
grep -rhE '^(deb|Suites:|URIs:)' /etc/apt/sources.list /etc/apt/sources.list.d/ \
| sort -uEvery 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:
apt update
grep -rl bookworm /etc/apt/sources.list /etc/apt/sources.list.d/ || echo 'no bookworm references remain'
apt policy | head -30A 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
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
sync
systemctl rebootWatch 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
pveversion -v
cat /etc/debian_version
uname -r
systemctl --failed --no-pager
journalctl -b -p err --no-pager | tail -40pvecm status
pvecm nodes
corosync-cfgtool -s
pvesm status
ha-manager statusceph -s
ceph versions
ceph osd tree
ceph health detailceph 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
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 yamlMove 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:
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'
doneThen re-enable what you froze in Step 1 - backup jobs, replication jobs, automation - and run one of each by hand before trusting the schedule.
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 are | What rollback means |
|---|---|
| Before repository change | Nothing to undo |
Repositories changed, dist-upgrade not run | Restore /etc/apt from the Step 4 copy, apt update, done |
dist-upgrade running | Do not interrupt it. If it failed, dpkg --configure -a and apt -f install on the console before any reboot |
dist-upgrade complete | No downgrade exists. Forward to a working 9.x, or reinstall and rejoin |
| Node upgraded, guests unhappy | Migrate 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 unstable | Stop. Consolidate guests onto the healthiest set of nodes, then decide: finish forward, or rebuild the upgraded nodes at 8.4 |
Common patterns
| Symptom | Likely cause | Resolution |
|---|---|---|
apt update fails after the repository switch | A third-party repository with no Trixie suite | Disable it for the window; revisit after |
Node boots but /etc/pve is empty | pve-cluster did not start | systemctl status pve-cluster, then the pmxcfs recovery runbook |
| Guest will not start after the upgrade | Old machine type, removed device model, or passthrough IDs changed | qm showcmd VMID --pretty; see the VM start-failure runbook |
| Live migration fails between an upgraded and a non-upgraded node | Direction is 9 to 8 | Not supported. Migrate offline, or finish the upgrade |
| Ceph OSD does not come up after reboot | Ceph release was not Squid before the upgrade | Stop. Fix Ceph on the remaining 8.4 nodes first |
| Network unreachable after reboot | An interfaces file was overwritten or the NIC name changed | Console, restore from the Step 4 copy, ifreload -a |
pvecm status shows the node but the GUI shows it grey | Certificates not regenerated for the new state | pvecm updatecerts --force, then restart pveproxy |
Escalation
Escalate when:
- A node does not return from the reboot.
- Ceph reports anything other than
HEALTH_OKbetween nodes. - Quorum is lost at any point.
- A
pve8to9WARN 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.