How To

Migrate Ubuntu 22.04 LTS to Ubuntu 26.04 LTS (Skip-Release Upgrade)

Ubuntu’s release upgrade tool does not let you jump straight from 22.04 to 26.04. The supported path is two hops, 22.04 → 24.04 → 26.04, with a reboot between each one. It works, it keeps your data, and when it goes sideways the failures are predictable enough to fix in the same session.

Original content from computingforgeeks.com - post 166909

This guide walks through the full skip-release upgrade on a real server, not an empty cloud image. The test VM runs a LAMP stack with a PHP dashboard and a MariaDB inventory database holding 500 customers, 120 products, and 2000 orders. Every step below was executed on that workload and the data is re-validated after each hop, with fastfetch output captured on every release so you can compare kernel, shell, OpenSSH, and PHP versions side by side.

Verified working: April 2026 on a Proxmox KVM guest, upgrade path 22.04.5 (kernel 5.15.0-173) → 24.04.4 (kernel 6.8.0-110) → 26.04 “Resolute Raccoon” development branch (kernel 7.0.0-14)

Why two hops, not one

The do-release-upgrade helper reads /etc/update-manager/release-upgrades to decide what release to offer, and on an LTS install it only offers the next LTS. From 22.04 that is 24.04. Ubuntu does not publish a skip-release upgrade metadata file, so there is no supported “22.04 to 26.04” path.

The path that works is sequential. Upgrade 22.04 to 24.04, reboot, confirm your workload, upgrade 24.04 to 26.04, reboot, confirm again. The whole thing took about 55 minutes on the test VM over a gigabit link, of which around 10 minutes was package download and the rest was unpack and post-configure.

Two things to know before starting. First, at the time of writing, 26.04 is still pre-release (GA is April 23 2026), so the second hop needs do-release-upgrade -d to see it. Once 26.04.1 ships around August 6 2026, the -d flag is no longer needed. Second, if you are coming from 24.04 already, skip straight to the second hop.

Test workload used in this guide

An empty server upgrade tells you the upgrade tool works. A server with a real database, a real PHP frontend and real connection pools tells you whether the apps survive. The VM in this walkthrough runs:

  • Apache 2.4.52 serving a PHP dashboard at / and a JSON API at /api/stats.php
  • MariaDB 10.6.23 with a populated inventory database (three tables, foreign keys, indexes)
  • 500 customer rows, 120 product rows, 2000 order rows, total non-cancelled revenue of $6,347,514.05

After each upgrade the same curl http://localhost/api/stats.php is re-run. Row counts and the cancelled-order revenue sum must match to the cent, or the upgrade is a regression.

Step 1: Set reusable shell variables

Every command below uses shell variables for the backup path and admin email so you can copy the commands as-is. Export these at the top of your SSH session:

export BACKUP_DIR="/root/upgrade-backups"
export DB_NAME="inventory"
export ADMIN_EMAIL="[email protected]"
mkdir -p "${BACKUP_DIR}"
echo "Backups: ${BACKUP_DIR}"
echo "Database: ${DB_NAME}"

Replace DB_NAME with your own database name if you are following along with a different workload.

Step 2: Baseline the 22.04 host

Install fastfetch so you have a clean before-and-after view of kernel, shell, OpenSSH and PHP. It is not in the default 22.04 repos, grab the latest release from GitHub:

FF_URL=$(curl -sL https://api.github.com/repos/fastfetch-cli/fastfetch/releases/latest \
  | grep -oE 'https://[^"]+fastfetch-linux-amd64.deb' | head -1)
wget -q "${FF_URL}" -O /tmp/fastfetch.deb
sudo dpkg -i /tmp/fastfetch.deb
fastfetch --logo none

The output below is the baseline captured on the test VM. Note the kernel (5.15), shell (bash 5.1), OpenSSH (8.9p1) and PHP 8.1 signatures, those change on every hop.

fastfetch output on Ubuntu 22.04 LTS before the upgrade showing kernel 5.15, bash 5.1 and OpenSSH 8.9p1

Step 3: Back up the workload before the first hop

A full mariadb-dump of the app database plus a copy of /etc/apache2, /etc/php and the web document root is the minimum safety net. It is cheap and gives you a known-good restore point if the upgrade damages configuration:

sudo mariadb-dump "${DB_NAME}" > "${BACKUP_DIR}/${DB_NAME}-pre-upgrade.sql"
sudo tar czf "${BACKUP_DIR}/etc-pre-upgrade.tar.gz" /etc/apache2 /etc/php /etc/mysql 2>/dev/null
sudo tar czf "${BACKUP_DIR}/www-pre-upgrade.tar.gz" /var/www/html
ls -lh "${BACKUP_DIR}"

Take a hypervisor snapshot too if you are on Proxmox, VMware, or a cloud provider that supports them. The hypervisor-level snapshot is the only rollback that actually works for a broken distro upgrade. There is no supported apt downgrade path.

Step 4: Fully update 22.04 before upgrading

Two things the upgrade tool insists on. The kernel modules it is about to replace must match a kernel that is actually booted, and the package state has to be clean. If you have a pending reboot from a prior apt upgrade, do-release-upgrade refuses with a clear error:

sudo apt update
sudo apt -y upgrade
sudo apt -y autoremove
[ -f /var/run/reboot-required ] && echo "REBOOT REQUIRED" || echo "no reboot needed"
sudo reboot

Reconnect after the reboot. If you skip the reboot and try to upgrade anyway, the tool exits with the message You have not rebooted after updating a package which requires a reboot. Please reboot before upgrading. This caught the first run on the test VM, adding five minutes to the timing.

Step 5: First hop, 22.04 to 24.04

Set the upgrade channel to lts, confirm the release offered, then run the upgrade. Use the non-interactive frontend so it does not stall on Apache, Nginx or SSH config prompts, it keeps local versions by default:

sudo sed -i 's/^Prompt=.*/Prompt=lts/' /etc/update-manager/release-upgrades
sudo do-release-upgrade -c

You should see New release '24.04.4 LTS' available. Run 'do-release-upgrade' to upgrade to it. Kick off the actual upgrade in the non-interactive frontend. This takes roughly 20 to 30 minutes depending on CPU, network and disk speed:

sudo DEBIAN_FRONTEND=noninteractive do-release-upgrade \
  -f DistUpgradeViewNonInteractive

Three things to know while it runs. SSH will briefly reject connections when openssh-server is reconfigured, give it 90 seconds and it comes back. Apache gets restarted multiple times while new modules are unpacked. The tool logs every config file it replaces under /var/log/dist-upgrade/, worth a grep afterwards for Installing new version of config file to see what changed.

When it finishes the tool does not reboot for you under DistUpgradeViewNonInteractive, reboot manually:

sudo reboot

Step 6: Fix the Apache PHP module gotcha

This is the one predictable failure in a 22.04 → 24.04 hop on any LAMP box. 22.04 ships PHP 8.1, 24.04 ships PHP 8.3. The upgrade installs libapache2-mod-php8.3 but leaves the old php8.1.load and php8.1.conf symlinks in /etc/apache2/mods-enabled/, so Apache on the first boot after the upgrade fails with:

apache2: Syntax error on line 146 of /etc/apache2/apache2.conf:
Syntax error on line 3 of /etc/apache2/mods-enabled/php8.1.load:
Cannot load /usr/lib/apache2/modules/libphp8.1.so into server:
/usr/lib/apache2/modules/libphp8.1.so: cannot open shared object file:
No such file or directory

The fix is two a2*mod calls and a restart. No manual editing:

sudo a2dismod php8.1
sudo a2enmod php8.3
sudo systemctl restart apache2
systemctl is-active apache2
Apache PHP module error after the 22.04 to 24.04 upgrade and the a2dismod/a2enmod fix

On the test VM the output from systemctl is-active apache2 went from failed to active in under two seconds. The workload curl http://localhost/api/stats.php returned php_version: 8.3.6, server: Linux 6.8.0-110-generic and the same 500 / 120 / 2000 row counts as before the upgrade.

Step 7: Validate the workload before the second hop

Never run the 24.04 → 26.04 hop until you have confirmed the workload on 24.04 is working. A broken app on 24.04 will not magically heal on 26.04, it will be twice as hard to diagnose with two sets of changes mixed together. Run the app’s health checks, read the logs, exercise the write path:

systemctl is-active apache2 mariadb
curl -s http://localhost/api/stats.php | python3 -m json.tool
sudo mariadb -e "SELECT (SELECT COUNT(*) FROM ${DB_NAME}.customers) c, \
  (SELECT COUNT(*) FROM ${DB_NAME}.products) p, \
  (SELECT COUNT(*) FROM ${DB_NAME}.orders) o, \
  (SELECT ROUND(SUM(total),2) FROM ${DB_NAME}.orders WHERE status<>'cancelled') rev"

On the test VM the fastfetch snapshot after the first reboot looks like this, PHP is 8.3, kernel is 6.8, OpenSSH is 9.6p1:

fastfetch output on Ubuntu 24.04 LTS after the first upgrade hop showing kernel 6.8, bash 5.2 and OpenSSH 9.6p1

Step 8: Second hop, 24.04 to 26.04

Until 26.04.1 ships on August 6 2026, do-release-upgrade on a plain LTS channel will say “No new release found.” The -d flag tells it to include development releases:

sudo apt update
sudo apt -y upgrade
sudo apt -y autoremove
sudo do-release-upgrade -c --devel-release

If that prints New release '26.04 LTS' available. you are good to proceed. Run the full upgrade with the same non-interactive frontend as before, and the -d flag to pick up 26.04:

sudo DEBIAN_FRONTEND=noninteractive do-release-upgrade -d \
  -f DistUpgradeViewNonInteractive

This is the longer of the two hops because 26.04 brings a much larger change: kernel 7.0, systemd 259, PHP 8.5, Apache 2.4.66, bash 5.3, OpenSSH 10.2 with post-quantum KEX enabled by default, Rust coreutils replacing GNU coreutils on fresh installs, and cgroup v1 dropped entirely from the stack. Total download is around 1.4 GB for a LAMP host like this one.

Reboot once it completes:

sudo reboot

Step 9: Validate on 26.04

Unlike the first hop, the 24.04 → 26.04 transition handled the Apache PHP module swap cleanly on the test VM, because 24.04 → 26.04 is a single PHP minor step (8.3 → 8.5) and the postinst scripts in libapache2-mod-php8.5 disable the older 8.3 module automatically. Worth checking anyway:

ls /etc/apache2/mods-enabled/ | grep php
systemctl is-active apache2 mariadb
curl -s http://localhost/api/stats.php | python3 -m json.tool

If mods-enabled still shows a php8.3.load symlink, run sudo a2dismod php8.3 && sudo a2enmod php8.5 && sudo systemctl restart apache2 and re-check.

Here is the fastfetch snapshot at the end of the second hop. Everything on the test VM now reports 26.04 signatures, the workload kept its data, and the PHP dashboard renders against a kernel 7.0 host:

fastfetch output on Ubuntu 26.04 LTS after the second upgrade hop showing kernel 7.0, bash 5.3 and OpenSSH 10.2p1

And the JSON API response captured on all three OS versions back to back, same counts, same revenue, different PHP and kernel strings:

JSON API response from the PHP dashboard on Ubuntu 22.04, 24.04 and 26.04 showing identical customer/product/order counts and identical revenue

The PHP-rendered dashboard is identical against the live 26.04 host, top-5 products by revenue, 500 customer rows, 120 products, 2000 orders, served by Apache 2.4.66 with libphp8.5.so:

PHP inventory dashboard rendered on Ubuntu 26.04 LTS with PHP 8.5.4 on kernel 7.0 showing the same 500/120/2000 counts and revenue totals

Step 10: Post-upgrade cleanup

Four cleanup actions that keep the box tidy. Purge the old kernel images that do-release-upgrade leaves around, remove obsolete package configs, rebuild the initramfs so Dracut is consistent, and update GRUB:

sudo apt -y autoremove --purge
sudo apt -y autoclean
dpkg -l | awk '/^rc/ {print $2}' | xargs -r sudo dpkg --purge
sudo update-initramfs -u -k all
sudo update-grub
df -h /boot /

On the test VM, / went from 2.4 GiB used on 22.04 to 4.4 GiB used on 26.04 after cleanup. The jump is almost entirely from bigger kernel modules plus the x86-64-v3 optimised package set that 26.04 pulls in on AVX2-capable CPUs.

Troubleshooting the real errors that came up

Three errors hit the test VM during this upgrade. All three are in the /var/log/dist-upgrade/main.log tail so you can grep for the same strings on your own runs.

Error: “You have not rebooted after updating a package which requires a reboot”

This blocked the first do-release-upgrade invocation. Cause: an earlier apt upgrade installed a new 5.15 kernel and the VM was still running the old one. Fix: sudo reboot, reconnect, retry. If you cannot reboot the host, run sudo unattended-upgrade --dry-run -d to see what still needs a restart and clear it first.

Error: “Cannot load /usr/lib/apache2/modules/libphp8.1.so”

Apache refused to start after the first reboot onto kernel 6.8. The upgrade installed libapache2-mod-php8.3 but left mods-enabled/php8.1.load pointing at a missing shared object. Fix: sudo a2dismod php8.1 && sudo a2enmod php8.3 && sudo systemctl restart apache2. If you run a different PHP extension stack (e.g. PHP-FPM via FastCGI), also re-check /etc/apache2/conf-enabled/php*-fpm.conf.

Warning: “Download is performed unsandboxed as root”

Harmless, prints once during the metadata fetch. It is a systemd hardening warning from a tightened apt sandbox profile in 24.04+ when the upgrade tool runs as root. Ignore it, the download still verifies the GPG signature.

Series navigation

Related Ubuntu 26.04 guides on computingforgeeks:

Related Articles

Desktop How To Install IntelliJ IDEA on Ubuntu 22.04|20.04|18.04 Monitoring Install Dynatrace ActiveGate on Ubuntu 24.04 / Rocky Linux 10 Databases Install Redis 8.0 on Ubuntu 26.04 LTS Networking GNS3 Installation Guide for Ubuntu 24.04 (Fast & Easy)

Leave a Comment

Press ESC to close