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:
- Replace it with a supported alternative.
- Wait for an EL10 package if the software is non-critical.
- 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
- 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.
- Snapshot at every major-version boundary. EL8 → EL9 and EL9 → EL10 are two separate migrations. Treat them that way.
- Keep configuration backups outside the package manager. Especially custom PHP-FPM pools, web-server vhosts, database config and signing keys.
- Check numeric ownership after upgrades. If files suddenly show as
UNKNOWN:UNKNOWN, you probably lost a local user/group rather than file permissions. - 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
- AlmaLinux ELevate project
- AlmaLinux ELevate Quickstart Guide
- AlmaLinux staged ELevate guide (includes EL8 → EL9 → EL10 cleanup notes)
- AlmaLinux 10.2 release notes
Commands and package names reflect AlmaLinux/ELevate as of August 2026. Always read the current ELevate report and documentation before upgrading a production system.