AlmaLinux 8 → 9 → 10 with ELevate: The Upgrade That Actually Worked

Introduction, or “How Bad Could Two Major Upgrades Be?”

You have an AlmaLinux 8 server. It works. It has been working for years. Naturally, this means the most sensible thing you can do is upgrade it through two major operating-system releases without reinstalling it.

AlmaLinux has ELevate for exactly this job. Under the hood it uses Leapp to perform supported in-place upgrades between Enterprise Linux major versions.

The important part is that this is not an AlmaLinux 8 → 10 upgrade. Leapp works one major release at a time:

AlmaLinux 8 → AlmaLinux 9 → AlmaLinux 10

Do not skip the middle step. Do not treat AlmaLinux 9 as a temporary boot screen on the way to 10. Upgrade to 9, boot it, clean it up, verify the services, take another snapshot, and only then continue to 10.

This guide is based on a real production-style upgrade of an old server with Apache, MariaDB, third-party repositories, custom packages and enough historical baggage to qualify as an archaeological site.

The good news: it worked. The less-good news: the actual upgrade was the easy part. The interesting problems were all the things accumulated around the OS over the years.


Before You Touch Anything

First: take a snapshot or a full backup. Preferably both if this is important infrastructure.

Then make a small migration bundle. You want to know exactly what was installed and have copies of the configuration that matters if a package gets replaced, removed or “helpfully” modernised.

mkdir -p /root/elevate-backup

rpm -qa | sort > /root/elevate-backup/packages-before-elevate.txt
dnf repolist --enabled > /root/elevate-backup/repos-before-elevate.txt

cp -a /etc/httpd /root/elevate-backup/ 2>/dev/null || true
cp -a /etc/nginx /root/elevate-backup/ 2>/dev/null || true
cp -a /etc/php* /root/elevate-backup/ 2>/dev/null || true
cp -a /etc/my.cnf* /root/elevate-backup/ 2>/dev/null || true
cp -a /etc/opt /root/elevate-backup/ 2>/dev/null || true
cp -a /root/.gnupg /root/elevate-backup/ 2>/dev/null || true

If the machine runs MariaDB/MySQL, take a logical dump too:

mariadb-dump --all-databases \
  --single-transaction \
  --routines --events --triggers \
  > /root/elevate-backup/all-databases.sql

Also check disk space. Leapp needs room to download packages and build the target userspace. If your root filesystem is already at 90%, this is not the time to discover how optimistic you were when you partitioned the VM in 2015.

df -h /

Step 0 — Fully Update the Current OS

ELevate expects the source system to be fully updated.

dnf update -y
reboot

After reboot:

cat /etc/redhat-release
uname -r
dnf check
systemctl --failed

If the package database is already unhappy or services are already broken, fix that first. An in-place major upgrade is a poor troubleshooting technique.


Part 1 — AlmaLinux 8 → AlmaLinux 9

1. Install ELevate and Leapp

dnf install -y \
  http://repo.almalinux.org/elevate/elevate-release-latest-el$(rpm --eval %rhel).noarch.rpm

dnf install -y leapp-upgrade leapp-data-almalinux

Then run the pre-upgrade analysis:

leapp preupgrade

This does not upgrade the machine. It produces the report you should actually read:

less /var/log/leapp/leapp-report.txt

Understanding the Leapp Report

Leapp reports warnings at several severity levels, but the word that matters most is:

INHIBITOR

An inhibitor is a hard stop. The upgrade will not proceed until it is resolved.

High-severity warnings are different. They may be very important — unsupported hardware, changed behaviour, unsigned third-party packages, boot-loader changes — but they are not automatically blockers.

Read the report. Fix every inhibitor. Understand the high-severity warnings. Then run leapp preupgrade again until:

Inhibitors: 0

The VDO Inhibitor

One common EL8 → EL9 inhibitor is Leapp asking whether the machine uses VDO storage.

Do not blindly answer it. First inspect the storage:

lsblk -f
dmsetup ls

If the system genuinely has no VDO volumes and Leapp is only waiting for confirmation, answer the generated question:

leapp answer --section check_vdo.confirm=True

Then:

leapp preupgrade

The lesson here is simple: Leapp answer files are not “yes to everything” files. They exist because the tool needs the administrator to make a decision it cannot safely infer.


Run the 8 → 9 Upgrade

Once there are no inhibitors:

leapp upgrade
reboot

The reboot will enter the temporary ELevate upgrade initramfs and perform the actual package transition.

When the system returns, verify that you are really on EL9:

cat /etc/redhat-release
uname -r
dnf check
systemctl --failed

Do Not Immediately Continue to EL10

This is the part I would strongly recommend even if you enjoy danger.

Stop on AlmaLinux 9 and treat it as a real migration milestone.

Check for packages left behind from EL8:

rpm -qa | grep -E '\.el8([._]|$)' | sort

Check the services you actually care about:

systemctl --failed
systemctl status httpd mariadb --no-pager
apachectl configtest

Obviously adjust that list to your machine.

If you use PHP, Python runtimes, database repositories, EPEL, Remi, Docker or other third-party repositories, verify them now. A service being active only proves that systemd managed to start it. It does not prove that your application still has the modules it had yesterday.


MariaDB: “It Started” Does Not Mean “The Upgrade Is Finished”

Major EL upgrades can also move you between major MariaDB releases.

After the OS upgrade:

mariadb -NBe 'SELECT VERSION();'
systemctl status mariadb --no-pager
journalctl -u mariadb -b --no-pager | tail -100

If MariaDB tells you the data directory needs upgrading, believe it:

mariadb-upgrade
systemctl restart mariadb

Then verify again:

mariadb -NBe 'SELECT VERSION();'

This is exactly the kind of post-upgrade task that is easy to miss because the daemon can be running while still telling you, politely, that there is unfinished work.


Third-Party PHP: Check the Extensions, Not Just php -v

If you use a parallel PHP stack such as Remi SCL packages, check the actual package release tags and modules.

rpm -qa | grep -E '^php|^php[0-9]+' | sort
php -v
php -m

For an SCL/parallel runtime, call that runtime directly as well:

/opt/remi/php82/root/usr/bin/php -v
/opt/remi/php82/root/usr/bin/php -m

A dependency cleanup can remove extensions while leaving the main PHP package perfectly healthy. gd, mbstring, zip, mysqli and friends are the things your application notices.


Before 9 → 10: Clean the EL8 Archaeology

If the machine arrived at AlmaLinux 9 via ELevate, check /etc/yum.conf and /etc/dnf/dnf.conf for Leapp/ELevate excludes.

ELevate may have added entries similar to:

exclude=snactor,python3-leapp,leapp-upgrade-el8toel9,leapp,...

Those can prevent you from removing the old upgrade stack and old EL8 packages.

Remove the obsolete Leapp/ELevate-related names from the exclude line, then clean up remaining EL8 packages deliberately.

rpm -qa | grep -E '\.el8([._]|$)' | sort

Do not turn this into:

rpm -e --nodeps $(everything-that-looks-old)

That is not cleanup. That is a future blog post.

Use dnf remove where possible and inspect the transaction before accepting it.

Once the EL9 system is clean and your applications are working, take another snapshot.


Part 2 — AlmaLinux 9 → AlmaLinux 10

First: Check the CPU

Standard AlmaLinux 10 x86_64 follows the RHEL 10 x86-64-v3 baseline. AlmaLinux also provides an x86-64-v2 build for older hardware, but the upgrade path and third-party package story are different; x86-64-v2 upgrades currently use ELevate NG.

In virtual machines, also check what CPU model the hypervisor exposes. A perfectly capable physical CPU can look ancient to the guest if the VM is configured with an old generic CPU type.

Read the Leapp CPU warnings carefully before continuing.


The EL10 Network-Scripts Surprise

One of the most useful inhibitors you can get on the 9 → 10 path is:

Legacy network configuration found

If your interface is still configured through an old file such as:

/etc/sysconfig/network-scripts/ifcfg-ens18

migrate it to a native NetworkManager connection profile before the upgrade.

First inspect what NetworkManager currently sees:

nmcli connection show

Back up the old files:

cp -a /etc/sysconfig/network-scripts \
  /root/network-scripts-pre-el10

Then migrate:

nmcli connection migrate

You should now have profiles under:

/etc/NetworkManager/system-connections/

Verify the machine still has the expected addresses, routes and connectivity before doing anything else:

nmcli connection show
ip addr
ip route

Then rerun:

leapp preupgrade

Remote servers are exciting enough without voluntarily upgrading the OS and network configuration at the same time while hoping SSH comes back.


Install/Refresh ELevate for the 9 → 10 Stage

Use the current AlmaLinux ELevate repository and target data:

dnf install -y \
  http://repo.almalinux.org/elevate/elevate-release-latest-el$(rpm --eval %rhel).noarch.rpm

dnf install -y leapp-upgrade leapp-data-almalinux

Then:

leapp preupgrade

Again: fix inhibitors, read the warnings, repeat until inhibitors reach zero.


Warnings You May See Before EL10

Not every scary-looking message is a blocker. Common categories include:

  • CPU models or CPU feature levels that are no longer maintained upstream
  • Packages not signed by the distribution
  • GRUB2/core boot-loader changes
  • Berkeley DB / libdb users
  • MariaDB or other database transitions
  • Third-party repository packages that do not exist for EL10

The important distinction remains the same: warning vs inhibitor.

But “not an inhibitor” does not mean “ignore it.” It means “Leapp will let you continue, so now the decision belongs to you.”


Run the 9 → 10 Upgrade

leapp upgrade
reboot

After the machine returns:

cat /etc/redhat-release
uname -r
df -h /
dnf check
systemctl --failed

At this point, seeing AlmaLinux 10 and a new kernel is a very good sign.

It is not the end.


Post-EL10 Cleanup: Find Everything Still Living in EL9

ELevate does not magically guarantee that every installed package has an EL10 equivalent. This matters especially with EPEL and third-party repositories. If a package existed on EL9 but has no EL10 build, it may remain installed.

Start with:

rpm -qa | grep -E '\.el9([._]|$)' | sort

Typical leftovers include:

  • the old EL9 kernel
  • Leapp/ELevate packages
  • old third-party packages
  • utilities that no longer exist on your architecture or in EL10 repositories

Do not remove the old kernel until you have confirmed the currently running kernel is EL10:

uname -r
rpm -qa | grep '^kernel' | sort

Once you are definitely running the EL10 kernel, remove the obsolete EL9 kernel packages with DNF.

Likewise, remove the old ELevate stack once the migration is finished. If DNF says the packages are filtered by exclude=, check /etc/yum.conf and /etc/dnf/dnf.conf again.


What If a Package Simply Does Not Exist on EL10?

This is where old utility servers become interesting.

You may discover that a perfectly ordinary package from EPEL9 has no EPEL10 binary package yet.

You have three sensible choices:

  1. Replace it with a supported alternative.
  2. Wait for an EL10 package if the software is non-critical.
  3. Rebuild the source RPM against EL10 if the software is simple and you are comfortable maintaining the package.

What you generally should not do is install the old EL9 binary RPM and pretend the migration is clean.

A typical local rebuild workflow looks like:

dnf install -y rpm-build rpmdevtools dnf-plugins-core

dnf builddep -y ./package-version.el9.src.rpm \
  --enablerepo=crb,epel

rpmbuild --rebuild package-version.el9.src.rpm

If the spec is portable and its dependencies exist on EL10, the resulting RPM will be built against the EL10 libraries and carry the EL10 dist tag.

This is much cleaner than dragging an old binary package forward indefinitely.


The Final Verification Checklist

Before calling the migration finished:

echo '=== OS ==='
cat /etc/redhat-release
uname -r

echo '=== OLD PACKAGES ==='
rpm -qa | grep -E '\.el9([._]|$)' | sort

echo '=== PACKAGE HEALTH ==='
dnf check

echo '=== FAILED SERVICES ==='
systemctl --failed

echo '=== DISK ==='
df -h /

Then test the application stack, not just systemd:

  • HTTP/HTTPS requests return the expected status
  • Apache/nginx configuration tests pass
  • PHP-FPM sockets/pools actually exist
  • required PHP modules are loaded
  • database queries work
  • repository metadata tools can read and update their existing data
  • signing keys, cron jobs and custom scripts still exist
  • the machine survives one more normal reboot

That last reboot matters. A server that works only because you have not rebooted since manually fixing something is not upgraded. It is temporarily cooperative.


Things I Would Do Differently Next Time

  1. Inventory third-party packages before starting. The OS packages are the boring part. The weird old repository tool installed six years ago is where your evening disappears.
  2. Snapshot at every major-version boundary. EL8 → EL9 and EL9 → EL10 are two separate migrations. Treat them that way.
  3. Keep configuration backups outside the package manager. Especially custom PHP-FPM pools, web-server vhosts, database config and signing keys.
  4. Check numeric ownership after upgrades. If files suddenly show as UNKNOWN:UNKNOWN, you probably lost a local user/group rather than file permissions.
  5. Read old logs. Not every problem you discover after an upgrade was caused by the upgrade. Sometimes the migration is merely the first time anyone looked closely at the machine in years.

Conclusion, or “Surprisingly, It Booted”

An AlmaLinux 8 → 9 → 10 in-place upgrade is completely practical when the machine is reasonably conventional and you treat Leapp as an audit tool rather than a magic upgrade button.

The actual pattern is boring:

backup
update
preupgrade
fix inhibitors
upgrade
reboot
verify
clean old packages
verify applications
snapshot
repeat

The difficult part is everything the server collected while nobody was looking: obsolete repositories, old kernels, custom users, forgotten PHP pools, packages that disappeared from EPEL, and services that were already broken before you started.

Which, honestly, is the most sysadmin thing possible.


References

Commands and package names reflect AlmaLinux/ELevate as of August 2026. Always read the current ELevate report and documentation before upgrading a production system.