In-place Ubuntu LTS upgrade on a remote box
2026-11-01
An in-place distribution upgrade is the one routine task where the failure mode is "you are locked out and the machine is half-migrated". Everything below exists to avoid that single outcome.
Before touching anything
- Console access that is not SSH. If the provider has no serial console or rescue mode, do not do this in place — rebuild instead and restore.
- A snapshot you have booted. A snapshot you have never restored from is a hope, not a backup.
- Bring the current release fully up to date and reboot first, so the upgrade starts
from a clean kernel and package set:
apt update && apt full-upgrade && reboot - Record the current state somewhere off-box:
dpkg -l > ~/packages-$(lsb_release -cs).txt, plus a tarball of/etc. - Read the release notes for the target release for the packages you actually run. Not the marketing page — the notes section on database, mail and networking changes.
The upgrade itself
screen -S upgrade # or tmux: a dropped link must not kill the upgrade
do-release-upgrade
Three prompts deserve more thought than the rest:
- Keep the local version of the modified configuration file. Say no
the first time you see it for a file you did not write yourself; say no again for
files you did write, and diff afterwards. Install
meld/colordiffbeforehand so the diff is readable. - "Do you want to start the upgrade anyway" after the free-space check.
Free space is measured against the default layout; if you have a separate
/varor a large/var/cache, clean it yourself rather than trusting the estimate. - The SSH port 1022 fallback. The updater opens a second sshd on
1022 in case the main one breaks. Keep the session open until the upgrade is
finished, then verify 1022 is closed:
ss -ltnp | grep 1022
After the reboot
systemctl --failed
journalctl -b -p err --no-pager | head -40
apt -f install
apt-get -s full-upgrade | grep -Ei "REMOVED|held back"
needrestart -b # services still running old binaries or libraries
The list of things that survive the upgrade in a broken-but-quiet state, in rough order of how often I have met them:
- Database clusters left on the old version, still running, while the new client
tools are installed. Check
pg_lsclustersor the equivalent; the upgrade usually creates the new cluster and leaves the old one serving. - PHP-FPM pools pointing at a socket for a version that no longer exists.
- Network configuration written by hand in a format the new
systemd-networkdornetplanparses differently. Have the console open before you restart networking. - Kernel modules from the previous series that were built out of tree and simply did not get rebuilt.
- Timers that were masked during the upgrade and never unmasked.
Rollback
There is no supported rollback from a partially upgraded release. The realistic options are the snapshot, or a restore onto a fresh install of the old release. This is the reason the first section is longer than the second.
Tested on 22.04 → 24.04. The shape is the same for any pair.