Move WordPress to a Fresh Server with Zero Downtime

CMS

By Jennifer Webb

Updated on Aug 11, 2026

Move WordPress to a Fresh Server with Zero Downtime

What this migration covers

This tutorial shows how to move WordPress to a fresh server with a low-risk cutover plan: prepare the new dedicated server, copy the site, keep changes in sync, switch DNS, and verify the final result. It’s written for hosting customers who need a real production migration, not a staging exercise.

You’ll work from a dedicated server running a supported Linux release. If you’re choosing infrastructure for the move, start with HostnExtra dedicated servers here: https://hostnextra.com/dedicated-server. Use this process when you want to keep the old site online until the new server is tested and ready.

  • Best fit: WordPress sites with a database, uploaded media, and a custom theme or plugins.
  • Goal: reduce downtime during DNS cutover.
  • Assumption: you have shell access to both servers and control of the domain’s DNS.

Example placeholders used below: SERVER_IP=203.0.113.10, OLD_SERVER_IP=203.0.113.20, ADMIN_USER=deploy, DOMAIN_NAME=example.com, APP_DIR=/var/www/example.com, DB_NAME=wordpress, DB_USER=wpuser, DB_PASS='replace-with-a-strong-password'.

Step 1: connect to the new server and create a sudo user

Run the first SSH command from your local computer. Replace the IP address and username with your provider details. If your SSH port is not 22, change -p 22 to the custom port.

ssh root@SERVER_IP

This opens a root session on the fresh server. Keep it open until the new sudo user works.

In the VPS as root session, identify the operating system before choosing package commands.

cat /etc/os-release

You should see either Debian/Ubuntu or AlmaLinux/Rocky Linux details. Don’t skip this check; the next steps differ by family.

Still in the root session, create a non-root admin account. Replace deploy with your preferred username.

adduser deploy

This creates the home directory and prompts for a password. Set a strong password if you plan to allow password login during the first test phase.

Add the user to the sudo group on Debian-family systems:

usermod -aG sudo deploy

On AlmaLinux and Rocky Linux, use the wheel group instead:

usermod -aG wheel deploy

Now create the SSH directory and authorize your public key. Run these commands as VPS as root, replacing the sample key with your own public key from your local computer.

mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
cat > /home/deploy/.ssh/authorized_keys <<'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleReplaceThisWithYourRealKey local-admin
EOF
chmod 600 /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh

This sets the required ownership and permissions. If the key is wrong, SSH login will fail later, so replace it carefully.

From a second terminal on your local computer, test the new account before making any hardening changes.

ssh deploy@SERVER_IP

Once logged in, confirm sudo access:

sudo -v

You should be prompted for the deploy user’s password, then see no error. Keep the original root session open until this succeeds.

Step 2: update packages, time sync, and install the stack

Next, update the new server. Use the section that matches your OS family. These commands run in the VPS as root or VPS as the sudo user after you confirm sudo works.

Ubuntu and Debian

sudo apt update
sudo apt upgrade -y
sudo apt install -y nginx mariadb-server php-fpm php-mysql php-cli php-curl php-xml php-mbstring php-zip php-gd php-intl rsync unzip

This installs the web server, database server, PHP runtime, and the tools needed for the site copy. If your WordPress site uses extra plugins, add the matching PHP extensions later.

timedatectl status

Check that time synchronization is active. If it is not, enable system time sync:

sudo timedatectl set-ntp true

Start and enable services at boot:

sudo systemctl enable --now nginx mariadb php8.4-fpm

Replace php8.4-fpm with the PHP-FPM service installed on your system. Confirm the exact service name with:

systemctl list-units --type=service | grep -E 'php.*fpm'

AlmaLinux and Rocky Linux

sudo dnf update -y
sudo dnf install -y nginx mariadb-server php-fpm php-mysqlnd php-cli php-curl php-xml php-mbstring php-zip php-gd php-intl rsync unzip policycoreutils-python-utils

This installs the same WordPress runtime using RHEL-compatible package names. The SELinux tools package is included so you can label the site files correctly later.

timedatectl status

Confirm time sync is enabled. If needed:

sudo timedatectl set-ntp true

Start and enable services at boot:

sudo systemctl enable --now nginx mariadb php-fpm

Check the exact PHP-FPM service name with:

systemctl list-units --type=service | grep -E 'php.*fpm'

Step 3: create the database and copy WordPress files

Before you copy files, create the database and a dedicated database user. Run this on the new server as root or sudo user. Replace the password with a long unique value.

sudo mysql

At the MariaDB prompt, run the following SQL exactly:

CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'replace-with-a-strong-password';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

This creates the empty database that WordPress will use after the migration.

Now copy the files from the old server. Run this from the new server as root or sudo user. Replace the source path with the real WordPress directory on the old server.

sudo rsync -aHAX --delete root@OLD_SERVER_IP:/var/www/example.com/ /var/www/example.com/

This copies WordPress core, themes, plugins, and uploads. If the old server is not reachable by root SSH, use the equivalent admin account and path. Create the target directory first if needed:

sudo mkdir -p /var/www/example.com

Set ownership so the web server can read the site and your admin user can maintain it:

sudo chown -R www-data:www-data /var/www/example.com

On AlmaLinux and Rocky Linux, use the Apache-style web group if your Nginx/PHP setup expects it, commonly nginx:nginx or apache:apache depending on your PHP-FPM socket permissions. Check the existing packaging defaults before choosing ownership.

Step 4: move the database and update wp-config.php

Dump the old database from the source server. Run this on the old server as root or the account that can read the database. Replace credentials as needed.

mysqldump -u root -p --single-transaction --routines --triggers wordpress > /tmp/wordpress.sql

Copy the dump to the new server from your local computer or from the new server:

scp /tmp/wordpress.sql root@SERVER_IP:/tmp/wordpress.sql

Import it on the new server as root or sudo user:

sudo mysql wordpress < /tmp/wordpress.sql

Next, edit wp-config.php on the new server. Use your preferred editor; this example uses nano.

sudo nano /var/www/example.com/wp-config.php

Make sure the database settings match the new server:

define('DB_NAME', 'wordpress');
define('DB_USER', 'wpuser');
define('DB_PASSWORD', 'replace-with-a-strong-password');
define('DB_HOST', 'localhost');

Save and exit nano with Ctrl+O, then Enter, then Ctrl+X. If you use vim, save with :wq.

While you’re in the file, confirm that table_prefix matches the original site and that any hard-coded paths are correct.

Step 5: configure Nginx and firewall rules

Create the virtual host file on the new server as root or sudo user.

Ubuntu and Debian

sudo nano /etc/nginx/sites-available/example.com

Use this complete server block:

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
    }

    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg)$ {
        expires 30d;
        access_log off;
    }
}

Enable the site and test the configuration before reloading:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
sudo nginx -t
sudo systemctl reload nginx

AlmaLinux and Rocky Linux

sudo nano /etc/nginx/conf.d/example.com.conf

Use this server block, and adjust the PHP-FPM socket if your system uses a different path:

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php-fpm/www.sock;
    }
}

Test and reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

Open the firewall before testing from the browser.

Ubuntu and Debian firewall

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verbose

AlmaLinux and Rocky Linux firewall

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

If SELinux is enforcing on AlmaLinux or Rocky Linux, label the site directory and allow web access to the content:

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

This avoids silent 403 errors caused by wrong SELinux labels.

Step 6: switch DNS, lower risk, and test the live site

Before changing DNS, make one last content sync on the new server so recent posts, orders, or comments are not missed.

sudo rsync -aHAX --delete root@OLD_SERVER_IP:/var/www/example.com/ /var/www/example.com/

Then re-import only the database changes if the site had new activity during the migration window. For active stores, put the old site in maintenance mode first, or pause checkout briefly to avoid missed orders.

Update the A and AAAA records at your DNS provider to point to the new server IP. If you manage nameservers elsewhere, make the change there. Keep the TTL short before migration if possible so propagation is faster.

Now test the new server locally before you rely on public DNS. Run this from your local computer by forcing the Host header:

curl -I http://SERVER_IP -H 'Host: example.com'

You should see an HTTP 200 or a redirect to HTTPS, not a 502 or 403. If the response is wrong, check Nginx and PHP-FPM logs.

sudo journalctl -u nginx -n 50 --no-pager
sudo journalctl -u php-fpm -n 50 --no-pager

WordPress also keeps useful errors in its own log if enabled. Review any plugin or theme errors before you cut over.

Step 7: add TLS and finish cutover verification

After DNS points to the new server and the site resolves correctly, issue a Let’s Encrypt certificate on the new server as root or sudo user.

Ubuntu and Debian

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

AlmaLinux and Rocky Linux

sudo dnf install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Follow the prompt to choose the redirect to HTTPS. Then test renewal:

sudo certbot renew --dry-run

Finish with server and client checks. On the new server, confirm the services are running:

systemctl status nginx
systemctl status mariadb
systemctl status php-fpm

On your local computer, check the public site and the certificate:

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

Log into WordPress admin, confirm the site URL is correct, open a post, upload a test image, and load the homepage and a product page if WooCommerce is installed. Then reboot the server and confirm the stack comes back automatically:

sudo reboot

After reconnecting, repeat the status checks and confirm the site still loads. If the DNS switch was correct and the service starts cleanly, the migration is complete.

Troubleshooting and rollback

If the site shows a 502 error, run:

sudo systemctl status php-fpm
sudo ls -l /run/php/
sudo journalctl -u php-fpm -n 50 --no-pager

Look for a missing socket path or a failed PHP-FPM service. Fix the socket path in the Nginx config, then run sudo nginx -t and reload Nginx again.

If the site shows a 403 error on AlmaLinux or Rocky Linux, check:

getenforce
sudo ls -ldZ /var/www/example.com
sudo restorecon -Rv /var/www/example.com

If the labels are wrong, restore them and retest. If Nginx still denies access, review the server block and file ownership.

If you must roll back, point DNS back to the old server, keep the old database authoritative, and restore the last good file copy on the original host. Use the same rsync and database dump flow in reverse, then confirm the site on the old server before you close the incident.

Need a clean server for the next migration? Start with a dedicated server from HostnExtra and use the same tested cutover process for future WordPress moves: https://hostnextra.com/dedicated-server

Related HostnExtra guides

FAQ

Can I use this method for WooCommerce?
Yes. Pause new orders during the final sync window, copy files and database changes, then test checkout after cutover.

Do I need to change nameservers?
No. Usually you only change A and AAAA records at the DNS host. Move nameservers only if your DNS is changing providers.

Can I keep the old server online?
Yes. Keep it online until the new server is verified, and use it as rollback insurance during DNS propagation.

What if my server uses Apache instead of Nginx?
Use the same migration steps for files and database, then apply Apache virtual host and PHP-FPM settings instead of Nginx.

Move WordPress to a Fresh Server with Zero Downtime - HostnExtra