IPv4 & IPv6 Leasing - Any RIR, Any LocationOrder Now
Hostperl

WordPress Migration on RHEL 9 with Zero Downtime

By Raman Kumar

Share:

Updated on Sep 29, 2026

WordPress Migration on RHEL 9 with Zero Downtime

Plan the cutover before you move anything

This tutorial shows how to handle a WordPress migration on RHEL 9 with a short, controlled cutover. You will build the new host, sync the site files and database, check the Apache and PHP-FPM stack under SELinux, and switch DNS only after everything is ready.

The process is aimed at production sites that cannot afford a broken checkout page or a long maintenance window. If you are moving a live store, pair this guide with Hostperl VPS hosting at Hostperl VPS or a larger workload on dedicated server hosting if your traffic and database size justify it.

You will use RHEL 9 because it gives you SELinux, firewalld, and a stable support window. The same method also works on Oracle Linux 9 and Fedora Server with small package and service-name differences, but this tutorial keeps the commands tight for RHEL-compatible systems.

Before you start

Have these ready:

  • Root SSH access to the old server and the new RHEL 9 server.
  • Your WordPress document root path, database name, database user, and database password.
  • A maintenance window long enough to take one final database dump and switch DNS.
  • Backups you can restore if the final sync is interrupted.

If you need a general refresher on safe site moves, see WordPress migration checklist for safer store cutovers. For search visibility after the move, the file layout and redirects you choose will also affect crawlability, so keep technical SEO crawlability checks in mind.

Connect to the new server and identify the OS

On your local computer, start the SSH session with the documented example IP. Replace 203.0.113.10 with the public IP assigned to your new server.

ssh root@203.0.113.10

If your provider gave you a default non-root account, connect with that account instead and use sudo where shown later.

ssh deploy@203.0.113.10

On the VPS as root, confirm the distribution before installing packages.

cat /etc/os-release

You should see RHEL 9, Oracle Linux 9, or Fedora Server details. This guide uses RHEL 9 package names and services. On Oracle Linux 9 the commands are the same. On Fedora Server, package names are usually the same, but you should expect faster package updates.

Create a non-root admin and keep the root session open

Do not close your root session yet. Create a sudo-capable admin account first, then verify SSH access in a second terminal. Only after that should you consider disabling password login or root SSH.

On the VPS as root, create the administrator and add it to wheel.

useradd -m -G wheel deploy
passwd deploy

This creates the deploy account and sets its password. If you prefer SSH keys only, you can lock the password later.

On the VPS as root, prepare the SSH directory and permissions.

mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chown deploy:deploy /home/deploy/.ssh

On your local computer, copy your public key to the new account. Replace the example key file if yours uses a different name.

ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@203.0.113.10

On the VPS as root, secure the authorized keys file.

chown deploy:deploy /home/deploy/.ssh/authorized_keys
chmod 600 /home/deploy/.ssh/authorized_keys

On your local computer, open a second terminal and test the new account. Keep the root session open until this works.

ssh deploy@203.0.113.10
sudo -v

You should log in without using the root account, and sudo -v should accept your password or key-based sudo setup.

Install the web stack on RHEL 9

On the VPS as the non-root sudo user, update the server and install Apache, PHP, PHP-FPM, MariaDB client tools, rsync, and policy utilities used for SELinux-aware deployment.

sudo dnf update -y
sudo dnf install -y httpd php php-fpm php-mysqlnd php-gd php-xml php-mbstring php-opcache mariadb-server mariadb rsync policycoreutils-python-utils unzip

RHEL 9 ships a current PHP stack through AppStream. If your site needs a specific PHP version, add the appropriate repository before continuing. For most WordPress migrations, the default RHEL 9 PHP stream is sufficient.

On the VPS as the non-root sudo user, confirm the versions that were installed.

httpd -v
php -v
mysql --version

Apache should report a 2.4.x build, PHP should print its version, and the MariaDB client should respond with its release string.

Prepare the database on the new host

For a migration, create the target database and user before the final dump arrives. This reduces cutover time and makes the import step predictable.

On the VPS as the non-root sudo user, enable and start MariaDB.

sudo systemctl enable --now mariadb

On the VPS as the non-root sudo user, run the secure installation helper and set a root password when prompted.

sudo mysql_secure_installation

Accept the prompts that remove anonymous users, disallow remote root login, and remove the test database.

On the VPS as the non-root sudo user, create the WordPress database and user. Replace the sample password with a strong value you store safely.

sudo mysql -u root -p
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'ReplaceWithA_StrongPassword_2026!';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

You should now have an empty database ready for the import.

Move the WordPress files safely

Use rsync so the first copy can happen while the site is still live. That keeps the final maintenance window shorter.

On the VPS as the non-root sudo user, create a destination directory if your Apache document root is not already present.

sudo mkdir -p /var/www/wordpress

On your local computer or on the old server, mirror the existing WordPress files to the new host. Replace the source path with the old site path if it differs.

rsync -aHAX --delete /var/www/html/wordpress/ deploy@203.0.113.10:/var/www/wordpress/

If the old server is remote, run the rsync from the new server instead and pull the data over SSH. The important part is that the final file tree on the new host matches the old host.

On the VPS as the non-root sudo user, set ownership for Apache.

sudo chown -R root:root /var/www/wordpress
sudo find /var/www/wordpress -type d -exec chmod 755 {} \;
sudo find /var/www/wordpress -type f -exec chmod 644 {} \;

If uploads need write access, grant it only to the directories WordPress actually uses, not the full tree.

Import the database from the old server

Take the final dump as close to cutover as possible. If the site is busy, briefly enable maintenance mode or pause checkout activity before exporting.

On the old server, create a compressed dump.

mysqldump -u root -p --single-transaction --routines --triggers wordpress | gzip > wordpress-final.sql.gz

On the old server, copy the dump to the new host.

scp wordpress-final.sql.gz deploy@203.0.113.10:/home/deploy/

On the VPS as the non-root sudo user, import the database.

gunzip -c ~/wordpress-final.sql.gz | mysql -u root -p wordpress

If the dump came from a managed environment and includes foreign key warnings or character-set oddities, inspect the import output before moving on. A clean import should finish without fatal errors.

Configure Apache, PHP-FPM, and SELinux

RHEL 9 uses SELinux by default, so the move will only stay stable if Apache can read the content and connect to the database when needed.

On the VPS as the non-root sudo user, create a virtual host file.

sudo tee /etc/httpd/conf.d/wordpress.conf >/dev/null <<'EOF'
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/wordpress

    <Directory /var/www/wordpress>
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog /var/log/httpd/wordpress-error.log
    CustomLog /var/log/httpd/wordpress-access.log combined
</VirtualHost>
EOF

Replace example.com with your real domain. If you are testing before DNS cutover, you can use the server hostname temporarily and switch it later.

On the VPS as the non-root sudo user, allow Apache to read the new tree under SELinux.

sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/wordpress(/.*)?"
sudo restorecon -Rv /var/www/wordpress

If WordPress needs to write to wp-content/uploads, set that path separately so you do not relax the entire site.

sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/wordpress/wp-content/uploads(/.*)?"
sudo restorecon -Rv /var/www/wordpress/wp-content/uploads

On the VPS as the non-root sudo user, make sure PHP-FPM is enabled and Apache can use it if your stack relies on it. For many RHEL 9 WordPress installs, PHP runs cleanly through Apache modules, but PHP-FPM gives better pool control on busier sites.

sudo systemctl enable --now php-fpm
sudo systemctl enable --now httpd

On the VPS as the non-root sudo user, test Apache syntax before reloading.

sudo apachectl configtest

You should see Syntax OK. Only then reload Apache.

sudo systemctl reload httpd

Open the firewall without cutting off SSH

Add the new web rules before touching anything else. Keep SSH open, then allow web traffic, then review the rule set.

On the VPS as the non-root sudo user, enable the firewall and permit SSH, HTTP, and HTTPS.

sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

You should see the three services in the active zone. Do not remove SSH rules until you have verified the new login path and web access from a second terminal.

Point WordPress at the new database

On the VPS as the non-root sudo user, create or edit wp-config.php with the new database credentials.

sudo cp /var/www/wordpress/wp-config-sample.php /var/www/wordpress/wp-config.php
sudo vi /var/www/wordpress/wp-config.php

Set these values inside the file:

define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wpuser' );
define( 'DB_PASSWORD', 'ReplaceWithA_StrongPassword_2026!' );
define( 'DB_HOST', 'localhost' );

Save and exit the editor. If you use vi, press Esc, type :wq, and press Enter.

For a production migration, also force secure salts from the WordPress secret-key service after the database import and before opening the site publicly. That reduces the risk of stale sessions being reused across the cutover.

Run the final checks before switching DNS

On the VPS as the non-root sudo user, confirm the services are active and listening.

sudo systemctl status httpd php-fpm mariadb --no-pager
sudo ss -tulpn | grep -E '(:80|:443|:3306)'

Apache and MariaDB should be active. If PHP-FPM is used, it should also be active. Port 80 should be listening before you point DNS.

On the VPS as the non-root sudo user, check the web response locally.

curl -I http://127.0.0.1

You should get an HTTP status line such as 200 OK or a temporary redirect once HTTPS is configured. If you receive a 403 or 500, inspect the Apache error log immediately.

sudo tail -n 50 /var/log/httpd/wordpress-error.log
sudo tail -n 50 /var/log/httpd/error_log

For WordPress-specific behavior, verify that the admin page loads and that uploads and permalinks work after you log in. If the site is a store, place a test order through checkout and confirm that confirmation emails and payment callbacks still arrive.

If you want a deeper look at WordPress cutovers and staging habits, compare this migration flow with WordPress staging on Debian 12 for safe store updates. The Linux family differs, but the operational logic is the same: copy first, validate second, cut over last.

Switch DNS after the new server passes local tests

Only change DNS when the new host is already serving content correctly. Lower the record TTL in advance if your current provider allows it, then update the A record to the new server IP.

From a client machine, confirm the DNS change once it propagates:

dig +short example.com
curl -I http://example.com

Replace example.com with your real domain. You should see the new server IP from dig and a valid HTTP response from curl.

Enable HTTPS after the move

Once the site serves correctly on the new server and DNS points to it, issue a certificate. On RHEL 9, certbot typically comes from EPEL.

On the VPS as the non-root sudo user, install Certbot and request a certificate.

sudo dnf install -y epel-release
sudo dnf install -y certbot python3-certbot-apache
sudo certbot --apache -d example.com -d www.example.com

Follow the prompts, choose the redirect option if you want HTTP to move to HTTPS, and then verify renewal is scheduled.

sudo systemctl status certbot-renew.timer --no-pager
sudo certbot renew --dry-run

Rollback path if something breaks

If the new server shows database errors, blank pages, or missing media, do not troubleshoot blindly. Put the old site back into service first, then fix the new host offline.

Rollback usually means:

  1. Restore DNS to the old IP if the cutover is recent and traffic must resume immediately.
  2. Reapply the old database dump or resync the changed uploads directory if the failure happened during import.
  3. Check SELinux denials with ausearch and Apache logs if only the new server fails.

On the VPS as the non-root sudo user, check for SELinux clues.

sudo ausearch -m AVC,USER_AVC -ts recent
sudo journalctl -u httpd -n 100 --no-pager

Unexpected access denials usually mean a mislabeled path or an upload directory that needs write access. Correct the label, then retest before another reload.

Troubleshooting the most likely failures

Apache will not start. Run:

sudo apachectl configtest
sudo journalctl -u httpd -n 50 --no-pager

Look for a missing ServerName, a typo in the vhost file, or a bad path. Fix the file, run apachectl configtest again, and reload only after it returns Syntax OK.

WordPress shows a database connection error. Run:

sudo mysql -u root -p -e "SHOW DATABASES;"
sudo grep -n "DB_" /var/www/wordpress/wp-config.php

If the database is missing, recreate it. If the credentials are wrong, correct wp-config.php and retest.

Pages load, but uploads fail. Run:

sudo ls -ldZ /var/www/wordpress/wp-content/uploads
sudo restorecon -Rv /var/www/wordpress/wp-content/uploads

If SELinux labels are wrong, restore them. If permissions are wrong, adjust ownership only on the upload path.

DNS points to the new server, but the old content still appears. Check caching layers and resolver state from a client machine:

dig example.com
curl -I https://example.com

If you use a CDN or host-level cache, purge it after DNS changes. Otherwise you may think the migration failed when the browser is simply showing cached content.

Final verification from server and client

On the VPS as the non-root sudo user, confirm persistence after a reboot:

sudo systemctl is-enabled httpd php-fpm mariadb firewalld
sudo reboot

After the server comes back, reconnect and check the same services again. They should be enabled and active.

On your local computer, confirm the site is live, the certificate is valid, and the login path works.

curl -I https://example.com
curl -s https://example.com/wp-login.php | head

You should see a valid HTTPS response and the WordPress login page. That is the practical smoke test that the migration held together.

Hostperl can help if you want a cleaner move than a last-minute manual cutover. A Hostperl VPS gives you a controlled RHEL-compatible target for WordPress, and dedicated server hosting is a better fit when your store outgrows VPS resources or needs more storage headroom.

If you are planning a regional launch or a migration that needs support during the window, keep the DNS, SSL, and rollback steps documented before you schedule the change. That saves time when the site is under pressure.

FAQ

Can I use this migration flow on Oracle Linux 9?

Yes. Oracle Linux 9 follows the same package manager, service model, SELinux behavior, and firewall approach used here.

Do I need PHP-FPM for every WordPress site?

No. Smaller sites can run well with Apache and PHP modules, but PHP-FPM gives you better control on busier installs and is easier to isolate.

Why does SELinux matter during a WordPress migration?

Because a site can look correctly configured and still fail if Apache cannot read the files or write uploads. SELinux catches mistakes that would otherwise become silent permission problems.

What should I test before removing the old server?

Test the front page, wp-admin login, uploads, permalinks, checkout if applicable, HTTPS, and a full reboot on the new host. Only then retire the old server.

Can I shorten the DNS cutover time?

Yes. Lower the TTL before the migration, then switch the A record after the new server passes local tests. That reduces the time some users keep hitting the old site.