Two and a half years after CentOS 7 reached end of life (June 30, 2024) and over four years after CentOS 8 was abruptly cut short (December 31, 2021), many organisations still run unsupported CentOS hosts in production. The reasons are familiar: business systems built around the OS, expensive vendor recertification, conservative change windows, the comforting illusion that “it still works”. The reality is that the security patches stopped, the package archives are decommissioned, and a single unpatched glibc CVE can turn the host into a liability overnight. This playbook walks through the migration to Rocky Linux 9 (or AlmaLinux 9 — pick one; they are functionally interchangeable) using both the in-place ELevate path and the safer fresh-install path. We cover the pre-flight inventory you cannot skip, the gotchas between CentOS 7 / 8 and RHEL 9, and the post-migration validation that catches the silent failures.
Contents
Why still migrate in 2026
The numbers are unforgiving. CentOS 7’s last security update shipped in late June 2024. Every CVE landed since then is unfixed on the host you did not touch. The mirrors that still respond do so on community goodwill; the official Red Hat archive disappeared from the canonical URLs months ago. For workloads that handle payments, personal data, or anything subject to SOC2, ISO 27001 or PCI-DSS, running an EOL OS is a documented finding on every audit. Insurers have started carving out cyber coverage when the underlying OS is past EOL. The “if it ain’t broke” argument runs out at the audit table.
For CentOS 8 the story is worse because the cut-off was abrupt and the upgrade path to Stream changed the contract. Stream is rolling upstream of RHEL, not the stable downstream we used to call “CentOS”. Most organisations that ran CentOS 8 in production for stable use cases need to migrate to Rocky / Alma / RHEL, not stay on Stream.
Choosing the target: Rocky vs Alma vs RHEL
Three choices for a stable RHEL 9 equivalent in 2026.
| Option | Provenance | Cost | Best for |
|---|---|---|---|
| Rocky Linux 9 | Community-driven RHEL rebuild by the Rocky Foundation (founded by the original CentOS founder) | Free | The “default CentOS replacement” choice. Largest community, most documentation. |
| AlmaLinux 9 | Community-driven RHEL rebuild by CloudLinux | Free | Functionally identical to Rocky. Slightly faster security update cadence. Strong CloudLinux backing. |
| RHEL 9 | The original from Red Hat | Paid subscription (free Developer subscription for 16 hosts) | Workloads that need vendor support, regulated environments that require the upstream brand. |
Rocky and Alma are binary-compatible with each other and with RHEL. A package built for RHEL 9 installs cleanly on Rocky 9 and Alma 9 without rebuild. From an operational point of view the choice is governance: Rocky is community-foundation governed, Alma is CloudLinux foundation governed. Both have been stable since 2022. For most migrations, pick the one whose community fits your culture; technical capability is identical.
Two paths: in-place ELevate vs fresh install
You have two realistic migration strategies.
In-place ELevate
The AlmaLinux team maintains ELevate (github.com/AlmaLinux/leapp-data), a port of Red Hat’s Leapp tool that performs an in-place migration of an existing CentOS 7/8 host directly to Rocky 9, Alma 9, RHEL 9, or other RHEL-family targets. It analyses the system, flags blocking issues, then performs the upgrade in three reboots. ELevate works well for clean, vanilla installs. It works poorly for hosts with heavy third-party packages, custom kernels, or extensive manual configuration drift.
Fresh install + restore
Provision a new Rocky 9 / Alma 9 host, install applications from scratch (ideally via your configuration management), restore data from backup. Slower per-host but much more predictable, and forces you to actually have documented configuration management — which is the right state to be in.
The honest rule of thumb: use ELevate for hosts you have documented and tested, fresh-install for hosts that grew organically. Most production CentOS estates have a mix; the audit step below tells you which is which.
Pre-flight inventory and backup
Inventory installed packages
Generate a complete list of every installed package, broken out by source repository. This tells you which packages will have direct RHEL 9 equivalents and which need manual handling.
rpm -qa --qf "%{NAME}-%{VERSION}-%{RELEASE} %{VENDOR}\n" | sort > /tmp/packages.txt
yum repolist # CentOS 7
dnf repolist # CentOS 8
yum list installed | grep -v "@base\|@updates" > /tmp/third-party.txt
The /tmp/third-party.txt file is the list of packages from sources outside the distro defaults — these need attention during the migration. Common third parties: EPEL, Remi, ELRepo, vendor-specific (PostgreSQL official, MariaDB official, MongoDB).
Inventory configuration drift
Use rpm to list every config file that differs from the package default. These are the files you must verify in the target system because they may need to be ported to a new location.
sudo rpm -Va --nofiles --nomd5 --noscripts 2>/dev/null | sort > /tmp/drift.txt sudo find /etc -type f -newer /tmp/install-marker 2>/dev/null > /tmp/changed-config.txt
For the most critical files — sshd_config, sudoers, nginx.conf, my.cnf, postfix main.cf — generate a clean diff against a stock install of the same version, so the migration can mechanically apply the deltas.
Backup everything
Full disk image if your hypervisor / cloud provider supports it (Hetzner snapshot, AWS AMI, OVH snapshot, ESXi snapshot before VMware host conversion). Plus a logical backup of databases and application data with the recovery target verified.
sudo tar -czf /backup/etc-pre-migration.tar.gz /etc /var/spool/cron /root /home sudo mysqldump --all-databases | gzip > /backup/mysql-$(date +%F).sql.gz sudo -u postgres pg_dumpall | gzip > /backup/pg-$(date +%F).sql.gz sudo tar -czf /backup/var-www-$(date +%F).tar.gz /var/www
Document running services
You will reference this constantly during validation.
sudo systemctl list-units --type=service --state=running > /tmp/services-running.txt sudo ss -tlnp > /tmp/listening-ports.txt sudo crontab -l > /tmp/root-crontab.txt ls /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/ > /tmp/cron-jobs.txt
The ELevate in-place migration
ELevate runs as a Python tool that walks the host through a pre-upgrade analysis, an upgrade reboot, and post-upgrade reconciliation. The procedure for CentOS 7 → Rocky 9 (or Alma 9) in 2026:
Install ELevate
sudo yum install -y http://repo.almalinux.org/elevate/elevate-release-latest-el7.noarch.rpm sudo yum install -y leapp-upgrade leapp-data-rocky # for Rocky target # Or: sudo yum install -y leapp-upgrade leapp-data-almalinux # for Alma target
Pre-upgrade analysis
sudo leapp preupgrade
This generates /var/log/leapp/leapp-report.txt with three severity levels: inhibitor (blocks the upgrade), high (must review), medium/low (informational). Address every inhibitor before continuing. Common inhibitors: pinned packages from third-party repos, custom kernel modules, encrypted filesystems with non-standard setup.
Run the upgrade
sudo leapp upgrade sudo reboot
The upgrade runs in a special initramfs, so the actual transition happens after the reboot. It takes 15-45 minutes depending on disk speed and package count. The host reboots a second time on its own once the upgrade completes.
Post-upgrade reconciliation
cat /etc/os-release # confirm Rocky 9 / Alma 9 uname -r # new kernel sudo dnf check-update # routine package updates available? sudo dnf upgrade -y # apply them sudo systemctl --failed # any service failed to start?
The fresh-install path
Provision a new Rocky 9 or Alma 9 host, install applications, restore data, switch the DNS or floating IP. The steps:
- Provision the new host with the same specs as the source (or larger). Apply baseline hardening — see our Ubuntu hardening checklist (the principles apply equally to RHEL family with command substitutions).
- Install applications from configuration management (Ansible, Salt, Puppet, Chef). If you do not have configuration management, this is the moment to introduce it — at minimum a simple Ansible playbook documenting what gets installed.
- Restore data from backup. Database first, application files second, configuration last.
- Validate as in the next section.
- Cut over traffic. DNS change, load balancer reweight, or floating IP move. Keep the old host running for 24 to 72 hours as a fallback.
- Decommission the old host once stable.
Post-migration validation
Whichever path you used, validate against the documentation you generated in pre-flight.
- Every running service from /tmp/services-running.txt is still running. Cross-check with
systemctl --failed. - Every port from /tmp/listening-ports.txt is still listening. Cross-check with
ss -tlnp. - Every cron job runs at least once. Tail /var/log/cron (CentOS 7) or journalctl (Rocky 9).
- Application smoke tests pass. The same end-to-end flow you would test on a deploy.
- External monitoring confirms the host. Uptime, TLS certificate, security headers — our Cyber Audit Suite covers this in 20 seconds.
- Backup the new host. Migrations sometimes break the existing backup hooks (paths changed, permissions different); confirm the next scheduled backup actually runs.
Common breaks and fixes
network-scripts removed
RHEL 9 removed /etc/sysconfig/network-scripts/ifcfg-* as the source of truth. Network configuration lives in NetworkManager keyfiles at /etc/NetworkManager/system-connections/. Migrate via nmcli or install the compatibility shim NetworkManager-initscripts-ifcfg-rh if you need to retain the legacy files temporarily. See our network configuration article for the translation patterns.
Python 2 gone
RHEL 9 ships Python 3.9 as the default and Python 3.11 as the alternate. Python 2 is not available. Any in-house scripts still on Python 2 must port — use 2to3 as a starting point but expect manual cleanup. The official Python migration guide covers the common cases.
iptables replaced by nftables (mostly)
iptables still exists on Rocky 9 but is the nft shim. Existing rule files load and work, but performance characteristics change at scale. See our firewall comparison for the migration patterns from iptables to native nftables.
Podman replaces Docker (almost)
RHEL 9 ships Podman 4.x by default. Docker is available via the Docker repository but is no longer the default container runtime. Most docker commands work via the podman alias (alias docker=podman); compose files load via podman-compose. Differences: Podman is daemonless and rootless-friendly; networking semantics differ slightly.
SELinux stricter defaults
RHEL 9 SELinux policies are tighter than RHEL 7. Custom applications that worked on CentOS 7 may hit AVC denials on Rocky 9. Use ausearch -m AVC -ts recent to find them, then audit2allow to generate the local policy module. Do not blanket-disable SELinux to “fix” the issue; that turns the host into a juicier target.
Older glibc symbols dropped
Some C libraries from CentOS 7 may not link cleanly on Rocky 9 due to glibc symbol versioning. Recompile from source, or use the compat-glibc packages if available. Statically linked binaries continue to work.
Database major version mismatch
CentOS 7 shipped MariaDB 5.5 and PostgreSQL 9.2; Rocky 9 ships MariaDB 10.5 and PostgreSQL 13. A logical backup (mysqldump, pg_dumpall) is portable; a raw filesystem copy is not. Always migrate databases via the logical route unless you have very strong reasons.
Need quick syntax checks on Rocky 9?
Our Linux Distro Reference shows command, file path, workflow and gotcha for Rocky 9 across all eight admin topics in one click.
FAQ
Should I migrate to Rocky 9 or Alma 9?
For new deployments either is fine; they are functionally identical. If your team already standardised on one in another part of the estate, stay consistent. Rocky has the larger community on Reddit and discussion forums; Alma has slightly faster security update cadence. Both maintain RHEL compatibility through 2032.
Is ELevate safe for production?
It is mature enough (versions 0.18+ in 2026) that the core conversion works reliably. The risk is in edge cases: heavy third-party RPMs, kernel customisations, unusual storage layouts. Always run on a snapshot first, validate, then run on production. Plan a maintenance window even if the steady-state expectation is no downtime.
Can I migrate CentOS 7 directly to Rocky 9, or must I go through 8?
ELevate supports CentOS 7 → RHEL family 8 and CentOS 7 → RHEL family 9 directly. Direct 7 → 9 is supported but skips two major version transitions in one operation; some installations prefer the staged 7 → 8 → 9 path for better diagnostic granularity. Fresh install bypasses the question entirely.
What about Oracle Linux as a target?
Oracle Linux 9 is also a RHEL-compatible rebuild and is a valid migration target. The trade-off is Oracle support (paid is excellent, free is unsupported community-wise) and the questions some procurement teams ask about Oracle as a vendor. If those are not an issue, Oracle Linux 9 works and ships its own free in-place upgrade tool.
How long does the migration take per host?
ELevate: 30 to 90 minutes of automated work plus an hour of pre-upgrade preparation and an hour of post-upgrade validation. Fresh install: half a day to a full day depending on how good your configuration management is. For a fleet of 50 hosts, plan 3 to 6 weeks calendar time for a phased migration.
What about CentOS Stream?
Stream is the rolling upstream of RHEL. Appropriate for engineers who want to test against the next RHEL release or contribute back. Not appropriate for stable production workloads that prioritise predictable patch cycles. If you used CentOS 7 / 8 in production for stability, Stream is not the migration target — Rocky, Alma or RHEL are.













