CentOS to Rocky Linux 9 migration is overdue for a lot of shops, because CentOS 7 hit end of life on June 30, 2024 and CentOS 8 died even earlier, abruptly, back on December 31, 2021. And yet here we are in 2026 and plenty of hosts still run those systems in production. The patches stopped. The official package archive is gone. One unpatched glibc CVE and that stable box becomes the soft target someone walks straight through. This playbook covers the move to Rocky Linux 9, or AlmaLinux 9 if you lean that way. Two routes: the in-place ELevate path, and the slower fresh install I honestly trust more. Plus the pre-flight inventory you cannot skip and the breaks that fail silently.
The short answer
Move off end-of-life CentOS 7 or 8 onto Rocky Linux 9 (or AlmaLinux 9) two ways:
the in-place ELevate path that upgrades a live host across three reboots, or the
slower fresh install plus restore that is more predictable. Either way the work that
saves you is the pre-flight inventory of packages, config drift, services and backups,
then a hard validation pass against those notes.
CentOS 7 hit end of life on June 30, 2024. That's two and a half years ago now. CentOS 8 died even earlier, abruptly, back on December 31, 2021. And yet here we are in 2026 and plenty of shops still run those hosts in production. I get why. Some business system got welded to the OS years ago, recertifying with the vendor costs real money, change windows are tight, and there's that quiet voice saying it still works so why poke it. But here's the thing nobody on that side likes hearing. The patches stopped. The official package archive is gone. One unpatched glibc CVE and that "stable" box becomes the soft target someone walks straight through. This playbook covers the move to Rocky Linux 9, or AlmaLinux 9 if you lean that way, both of which I'll get into. Two routes: the in-place ELevate path, and the slower fresh install I honestly trust more. Plus the pre-flight inventory you can't skip and the breaks that fail silently.
Why still migrate in 2026
The math doesn't care about your feelings here. CentOS 7's last security update shipped in late June 2024. Every CVE since then sits unfixed on the box you didn't touch. The mirrors that still answer do it on community kindness, and the official Red Hat archive vanished from the canonical URLs months back. Handle payments, personal data, anything that falls under SOC2 or ISO 27001 or PCI-DSS? An EOL OS is a written finding on every single audit. Some insurers now carve out cyber coverage the moment the underlying OS is past end of life. So the "if it ain't broke" line, which I've used myself plenty of times, just stops working once you're sitting across from an auditor.
CentOS 8 is the rougher case. The cut-off came fast, and the pivot to Stream quietly rewrote the deal. Stream rolls ahead of RHEL. It isn't the calm downstream thing we all used to call "CentOS". So if you ran CentOS 8 in production because you wanted boring and predictable, Stream is the wrong answer. You want Rocky, or Alma, or RHEL.
Choosing the target: Rocky vs Alma vs RHEL
You've basically got three stable RHEL 9 equivalents to pick from 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 drops onto Rocky 9 or Alma 9 with no rebuild. So at the technical level the choice is basically governance. Rocky answers to a community foundation. Alma sits under the CloudLinux foundation. Both have been rock solid since 2022. My take, and I could be wrong here, is that if you're replacing CentOS as a default you should just go Rocky. Same founder as the original CentOS, and the biggest community, which in practice means more search results when something breaks at 2am. Alma's a genuinely great pick too, and its security patches tend to land a bit sooner. Honestly the capability is identical, so go with the crowd your team will actually lean on.
Avoid: moving CentOS 7 or 8 onto CentOS Stream and calling it "stable". Stream sits upstream of RHEL. It gets changes before RHEL has stabilised them. Great for dev work and for feeding fixes back to RHEL. Wrong tool for the long-tail production boxes you're trying to finally settle down.
Two paths: in-place ELevate vs fresh install
Two realistic strategies. That's it.
In-place ELevate
The AlmaLinux team keeps up ELevate (github.com/AlmaLinux/leapp-data), a port of Red Hat's Leapp that upgrades a live CentOS 7/8 host in place, straight to Rocky 9, Alma 9, RHEL 9, or another RHEL-family target. It reads the system, flags what's blocking, then does the upgrade across three reboots. On clean vanilla installs it works well. On boxes stuffed with third-party packages and years of hand-edited config drift, maybe a custom kernel on top? It works poorly, and you'll feel it.
Fresh install + restore
Stand up a new Rocky 9 or Alma 9 host, install the apps from scratch (ideally through your config management), then restore the data. It's slower per box. It's also way more predictable, and it quietly forces you to actually have documented config management, which, let's be honest, is where you wanted to be anyway.
My rough rule, and it's held up across a lot of boxes: ELevate for hosts you've documented and tested, fresh install for the ones that just grew on their own. Most CentOS estates are a messy blend of both. The audit step right below sorts them out for you.
Pre-flight inventory and backup
Inventory installed packages
Get a full list of every installed package, split out by which repo it came from. That's what tells you which ones have a clean RHEL 9 equivalent and which ones you'll be wrestling by hand.
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
That /tmp/third-party.txt file holds everything that came from outside the distro defaults. Those are the packages that bite during a migration. Usual suspects: EPEL, Remi, ELRepo, and the vendor repos like PostgreSQL official, MariaDB official, MongoDB.
Inventory configuration drift
Let rpm tell you every config file that no longer matches its package default. Those are the ones you have to check on the target, because half of them moved to a new home in RHEL 9.
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 files you really can't get wrong, sshd_config and sudoers and nginx.conf and my.cnf and postfix main.cf, diff each one against a stock install of the same version. Then the migration just replays the deltas instead of you guessing.
Backup everything
Take a full disk image if your hypervisor or cloud provider lets you (Hetzner snapshot, AWS AMI, OVH snapshot, an ESXi snapshot before any VMware host conversion). Then a logical backup of the databases and app data on top. And actually test that you can restore it. A backup you've never restored is just a hope.
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'll be glancing back at this the whole way through 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 is a Python tool that walks the host through three phases: a pre-upgrade analysis, the upgrade reboot itself, then the cleanup afterward. Here's the CentOS 7 to Rocky 9 (or Alma 9) procedure as it stands 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 writes /var/log/leapp/leapp-report.txt, sorted into severity levels. inhibitor means it blocks the upgrade. high means review it before you move. medium/low is just for your information. Clear every inhibitor before you go further, no exceptions. Usually it's pinned packages from a third-party repo or custom kernel modules. Sometimes an encrypted filesystem set up in some non-standard way.
Run the upgrade
sudo leapp upgrade
sudo reboot
The real work happens inside a special initramfs after the reboot, not while you're watching the terminal. Budget 15 to 45 minutes, depending on disk speed and how many packages you've got. When it's done the host reboots itself a second time. Don't panic when it does.
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?
ELevate caveat: it doesn't handle every package gracefully. EPEL stuff and custom RPMs can need a manual cleanup afterward, same for anything with gnarly post-install scripts. Run it on a snapshot first, every time. And expect roughly one in four migrations to need some hands-on touch-up.
The fresh-install path
Stand up a fresh Rocky 9 or Alma 9 box, install the apps, restore the data, then flip the DNS or the floating IP. Here's how that goes.
- Provision the new host at the same specs as the source, or bigger. Lay down baseline hardening while you're at it. Our Ubuntu hardening checklist covers the thinking, and it carries over to the RHEL family fine once you swap the commands.
- Install the apps from config management (Ansible, Salt, Puppet, Chef). No config management yet? This is the moment to start. Even a scrappy little Ansible playbook that just records what gets installed beats nothing.
- Restore data from backup. Database first, then the application files, configuration last.
- Validate using the next section.
- Cut over the traffic with a DNS change or a floating IP move (or reweight the load balancer if you've got one). Leave the old host running 24 to 72 hours as your safety net.
- Decommission the old host once you're sure the new one's holding.
Post-migration validation
Doesn't matter which path you took. Validate against the notes you made in pre-flight. That's the whole point of having made them.
- 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 fires at least once. Tail /var/log/cron (CentOS 7) or journalctl (Rocky 9).
- Application smoke tests pass. The same end-to-end flow you'd run after any deploy.
- External monitoring actually sees the host. Uptime, the TLS cert, the security headers, all of it. Our Cyber Audit Suite checks all that in about 20 seconds.
- Back up the new host. Migrations love to quietly break the old backup hooks (paths moved, permissions shifted), so watch the next scheduled run with your own eyes and make sure it really fires.
Common breaks and fixes
network-scripts removed
RHEL 9 dropped /etc/sysconfig/network-scripts/ifcfg-* as the source of truth. Your network config now lives in NetworkManager keyfiles under /etc/NetworkManager/system-connections/. Convert it with nmcli, or, if you need to cling to the old files for a bit, install the compat shim NetworkManager-initscripts-ifcfg-rh. Our network configuration article has the translation patterns laid out.
Python 2 gone
RHEL 9 ships Python 3.9 by default with 3.11 as the alternate. Python 2 is just gone. Any in-house script still stuck on 2 has to be ported. Start with 2to3, sure, but don't kid yourself, there'll be manual cleanup after. The official Python porting guide handles the common cases.
iptables replaced by nftables (mostly)
iptables is still around on Rocky 9, but it's the nft shim now, not the real thing. Your old rule files load and behave, though the performance profile shifts once you're at scale. Our firewall comparison walks the iptables to native nftables move.
Podman replaces Docker (almost)
RHEL 9 ships Podman 4.x out of the box. Docker's still installable from the Docker repo, it's just not the default runtime anymore. Most of your docker muscle memory survives through the podman alias (alias docker=podman), and compose files run via podman-compose. What's different: Podman is daemonless and plays nicer rootless. The networking semantics drift a little, so test that part rather than assuming.
SELinux stricter defaults
SELinux policy on RHEL 9 is tighter than it was on RHEL 7. A custom app that ran happily on CentOS 7 can suddenly hit AVC denials on Rocky 9. Find them with ausearch -m AVC -ts recent, then build a local policy module with audit2allow. Please don't just disable SELinux to make the error go away. I've watched people do exactly that, and all it does is hand an attacker a softer box.
Older glibc symbols dropped
A few C libraries from CentOS 7 won't link cleanly on Rocky 9 thanks to glibc symbol versioning. Recompile from source, or reach for the compat-glibc packages if they're available for your case. Anything statically linked just keeps working, untouched.
Database major version mismatch
CentOS 7 came with MariaDB 5.5 and PostgreSQL 9.2. Rocky 9 jumps you to MariaDB 10.5 and PostgreSQL 13. A logical backup (mysqldump, pg_dumpall) travels across that gap. A raw filesystem copy does not, and trying it is how you corrupt a database at 3am. Go the logical route unless you've got a genuinely strong reason not to.
Sources and further reading
Frequently asked questions
Should I migrate to Rocky 9 or Alma 9?
For a new deployment, either is fine. They are functionally identical. If your team already settled on one somewhere else in the estate, just stay consistent and save yourself the debate. Rocky has the bigger community on Reddit and the forums, which matters more than people admit when you are stuck. Alma patches security a touch faster. Both hold RHEL compatibility through 2032. If you are genuinely undecided, I would nudge you to Rocky, but you will not regret either.
Is ELevate safe for production?
It is mature enough now (0.18 and up in 2026) that the core conversion is reliable. The risk hides in the edge cases, piles of third-party RPMs, say, or a customised kernel, or some unusual storage layout. So run it on a snapshot first, validate hard, then point it at production. And book a maintenance window anyway, even when you are expecting zero downtime, because the one time you do not is the time it bites.
Can I migrate CentOS 7 directly to Rocky 9, or must I go through 8?
ELevate handles both CentOS 7 to RHEL-family 8 and CentOS 7 straight to RHEL-family 9. The direct 7 to 9 jump works, but it is two major version transitions crammed into one move, so when something breaks you have got less to point at. Some teams stage it 7 to 8 to 9 just for cleaner diagnostics. And a fresh install sidesteps the whole question.
What about Oracle Linux as a target?
Oracle Linux 9 is a RHEL-compatible rebuild too, and a perfectly valid target. The catch is Oracle support (the paid tier is genuinely excellent, the free one leaves you on your own) plus the eyebrows some procurement teams raise at the word Oracle. If neither bothers you, Oracle Linux 9 works fine and even ships its own free in-place upgrade tool. Honestly though, for most shops replacing CentOS I would still reach for Rocky first.
How long does the migration take per host?
ELevate runs about 30 to 90 minutes of automated work, and then add roughly an hour of prep before and an hour of validation after. Fresh install is more like half a day to a full day, and how good your config management is decides which end you land on. Got a fleet of 50 hosts? Pencil in 3 to 6 weeks of calendar time for a phased rollout, not a weekend.