Back to tutorials
Tutorial

WordPress Migration Tutorial (2026): Move a Site to a New VPS with Rsync, Database Export, and a Low‑Downtime DNS Cutover

WordPress migration tutorial for 2026: move your site to a new VPS using rsync, DB export, SSL checks, and low‑downtime DNS cutover.

By Anurag Singh
Updated on Jun 29, 2026
Category: Tutorial
Share article
WordPress Migration Tutorial (2026): Move a Site to a New VPS with Rsync, Database Export, and a Low‑Downtime DNS Cutover

A WordPress move usually doesn’t fail because WordPress “can’t be moved.” It fails because a small dependency gets missed.

Common examples include a rewrite rule, a PHP extension, a cron job, a stray DNS record, or an SSL validation step.

This WordPress migration tutorial gives you a repeatable, low‑downtime process for moving a site to a VPS.

You’ll do it without guessing and without keeping the site down longer than necessary.

The steps assume a common stack: Ubuntu 24.04/26.04 LTS class server, Nginx or Apache, PHP‑FPM, and MariaDB/MySQL.

Coming from shared hosting? Use the same flow. Export from the control panel, then pull the files and database onto the new server.

What you’ll build (and why this method works)

By the end, you’ll have:

  • A new VPS with the right web stack and a clean vhost/server block
  • A verified copy of wp-content plus a database import
  • A tested hosts-file preview (so you can confirm it works before DNS changes)
  • A short “final sync” window that keeps downtime low
  • A rollback plan that’s actually usable under pressure

This workflow also sets you up nicely if you plan to add a reverse proxy later.

If that’s next on your list, keep this bookmarked: Reverse proxy setup guide with Nginx in front of Apache/cPanel.

Prerequisites checklist (don’t skip this)

Before you touch files, get clear on what you’re migrating and who controls DNS.

Most “mystery downtime” happens when these basics are unclear.

  • Current site access: SSH/SFTP or file manager + database credentials (or phpMyAdmin access).
  • DNS access: You can edit A/AAAA records at your DNS host (domain registrar or DNS provider). If you need a domain, get it set up first via HostMyCode domains.
  • New server access: Root or sudo on the new VPS.
  • Maintenance window: 15–30 minutes for final sync and DNS cutover.
  • Backups: A restorable backup from the old host (files + DB). If you don’t already have one, stop and create it.

If you’d rather not manage updates, monitoring, and hardening yourself, a managed VPS hosting plan is often cheaper than learning these lessons mid-incident.

Step 1 — Provision the new VPS and do basic hardening

Pick a VPS size that matches your traffic and plugin stack.

For many WordPress sites with caching, 2 vCPU and 2–4 GB RAM is a sensible baseline.

WooCommerce, heavy page builders, or large media libraries usually need more headroom.

HostMyCode customers typically start here: HostMyCode VPS. You get predictable resources and full control of the stack.

On the new server, handle the basics first:

sudo apt update
sudo apt -y upgrade
sudo apt -y install ufw curl unzip rsync

Use SSH keys and disable password auth if possible.

If you want a guided baseline, follow: Server hardening tutorial for a new Ubuntu VPS.

Open only the ports you actually need:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

Step 2 — Install your web stack (Nginx or Apache) + PHP + database client

Choose one web server for migration day and keep it simple.

You can switch later.

The commands below use Nginx + PHP‑FPM. This is a common VPS setup for WordPress.

sudo apt -y install nginx
sudo apt -y install php8.3-fpm php8.3-mysql php8.3-xml php8.3-gd php8.3-curl php8.3-mbstring php8.3-zip php8.3-intl
sudo apt -y install mariadb-client

Notes for 2026:

  • php8.3 is widely deployed and stable. If your distro provides 8.4 and your plugin stack supports it, use 8.4 instead.
  • If your old server uses Imagick, install it now so image processing matches behavior: sudo apt -y install php-imagick

Prefer Apache? Keep the flow identical. Adjust only the vhost configuration.

Step 3 — Create the site directory, user, and a clean Nginx server block

Pick your canonical domain (example: example.com) and create a dedicated document root:

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

Create an Nginx server block at /etc/nginx/sites-available/example.com:

server {
  listen 80;
  server_name example.com www.example.com;

  root /var/www/example.com/public;
  index index.php index.html;

  access_log /var/log/nginx/example.com.access.log;
  error_log  /var/log/nginx/example.com.error.log;

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

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

  location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff2?)$ {
    expires 30d;
    add_header Cache-Control "public, max-age=2592000";
  }
}

Enable it and validate the config:

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

Keep it plain during the move.

Add aggressive caching, WAF rules, and extra redirects after the cutover.

Step 4 — Prepare the database on the destination (and record credentials)

You can run the database on the same VPS (common for small/medium sites) or on a separate database host.

This tutorial assumes a local database on the new VPS.

Install MariaDB server:

sudo apt -y install mariadb-server
sudo systemctl enable --now mariadb

Secure the install (set root password, remove test DB, etc.):

sudo mariadb-secure-installation

Create a database and user. Replace values carefully:

sudo mariadb
CREATE DATABASE wp_example DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'USE_A_LONG_RANDOM_PASSWORD';
GRANT ALL PRIVILEGES ON wp_example.* TO 'wp_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Store these three items somewhere safe: DB name, DB user, DB password.

You’ll need them for wp-config.php. Don’t be hunting for credentials during cutover.

Step 5 — Put the old site into “content freeze” mode (optional but recommended)

If the site changes often (orders, form submissions, uploads), plan a short freeze for the final sync.

A few practical options:

  • Enable a maintenance plugin right before final sync.
  • Temporarily disable comments and forms.
  • For WooCommerce, set the store to catalog mode and pause checkout.

This isn’t an hours-long maintenance window.

It’s a short pause that prevents missed orders and lost uploads during the last rsync.

Step 6 — Copy WordPress files with rsync (the fast, repeatable way)

From the new VPS, pull files from the old host. This keeps the process under your control.

It also avoids cases where the old host blocks outbound SSH.

Example (old server reachable by SSH):

sudo rsync -aHAX --numeric-ids --info=progress2 \
  -e "ssh -p 22" \
  olduser@OLD_SERVER_IP:/var/www/example.com/public/ \
  /var/www/example.com/public/

If you don’t have full paths on the old host (shared hosting), use SFTP download + upload.

Or export the home directory from the control panel and extract it on the VPS:

sudo unzip example.com-backup.zip -d /var/www/example.com/public

After the copy, set sane ownership and permissions:

sudo chown -R www-data:www-data /var/www/example.com/public
sudo find /var/www/example.com/public -type d -exec chmod 755 {} \;
sudo find /var/www/example.com/public -type f -exec chmod 644 {} \;

Pitfall: Don’t “solve” permissions by setting everything to 777. It works right up until it becomes a security incident.

Step 7 — Export the database from the old host (and import it cleanly)

If you have SSH on the old host, use mysqldump.

It’s consistent, repeatable, and easy to rerun for the final refresh.

mysqldump --single-transaction --quick --routines --triggers \
  -u OLD_DB_USER -p OLD_DB_NAME | gzip > /tmp/wp.sql.gz

Copy it to the new VPS:

scp olduser@OLD_SERVER_IP:/tmp/wp.sql.gz /tmp/wp.sql.gz

Import on the new VPS:

gunzip -c /tmp/wp.sql.gz | mariadb -u wp_user -p wp_example

If you must use phpMyAdmin export, choose:

  • Format: SQL
  • Enable: “Add DROP TABLE / VIEW / PROCEDURE”
  • Character set: utf8mb4

Step 8 — Update wp-config.php (and keep secrets out of Git)

Open /var/www/example.com/public/wp-config.php and set your new DB values:

define('DB_NAME', 'wp_example');
define('DB_USER', 'wp_user');
define('DB_PASSWORD', 'USE_A_LONG_RANDOM_PASSWORD');
define('DB_HOST', 'localhost');

If the new server needs different upload or memory limits, adjust PHP‑FPM.

On Ubuntu, the usual files are:

  • /etc/php/8.3/fpm/php.ini
  • /etc/php/8.3/fpm/pool.d/www.conf (if you need per‑pool changes)

Common WordPress-friendly defaults:

upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
max_execution_time = 120

Reload PHP‑FPM:

sudo systemctl reload php8.3-fpm

If you need to tune for traffic later, do it after the move.

That keeps variables isolated.

Use: PHP-FPM performance tuning tutorial.

Step 9 — Fix URLs safely (only if the domain changes)

If the domain stays the same, you can usually skip search/replace.

If the domain changes (or you’re moving HTTP→HTTPS), handle it deliberately.

Option A (recommended): WP‑CLI. Install:

curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php wp-cli.phar --info
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp

Run search-replace from the WordPress root:

cd /var/www/example.com/public
sudo -u www-data wp search-replace 'http://old-domain.com' 'https://new-domain.com' --all-tables --precise

Option B: Update only siteurl and home (safer for partial moves):

sudo mariadb -e "UPDATE wp_options SET option_value='https://example.com' WHERE option_name IN ('siteurl','home');" wp_example

Pitfall: Serialized data will break if you do a naive SQL string replace. WP‑CLI handles serialization correctly.

Step 10 — Test the new site before DNS cutover (hosts file method)

You want an end-to-end test while the public internet still hits the old server.

A local hosts-file override is the quickest way to do that.

On your laptop/desktop, map the domain to the new VPS IP.

  • Linux/macOS: edit /etc/hosts
  • Windows: edit C:\Windows\System32\drivers\etc\hosts

Add:

NEW_VPS_IP example.com www.example.com

Now browse the site and confirm the basics:

  • Homepage and a few deep pages
  • Login to /wp-admin
  • Media library loads images
  • Permalinks work (no 404s)
  • Contact forms, checkout flow (if applicable)

Keep the hosts entry in place through cutover.

Remove it after DNS is updated.

Step 11 — Install SSL and verify renewal paths

On a typical VPS, Let’s Encrypt via Certbot is the fastest route. Install and issue:

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

Confirm renewal is scheduled and actually works:

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

If validation fails (often after DNS or firewall changes), follow a structured troubleshooting path: SSL renewal troubleshooting tutorial.

Once SSL is working, add security headers carefully.

Don’t paste a random CSP from a forum thread.

Use: HTTP security headers tutorial.

Step 12 — Plan the DNS cutover (TTL, record audit, rollback)

Low downtime is mostly a DNS discipline problem.

Two things matter most: lower TTL ahead of time, and recreate every record you depend on.

  1. Audit current DNS records: A, AAAA, CNAME, MX, TXT (SPF/DKIM/DMARC), and any verification records.
  2. Lower TTL: 300 seconds (5 minutes) is a practical pre-cutover value. Do this at least a few hours before the move, ideally the day before.

If you want a full cutover workflow with checks and rollback, follow: DNS cutover tutorial.

Email note: If the domain also handles email, don’t casually edit MX/TXT records during a web move.

Some DNS providers apply “helpful” templates that wipe mail settings.

If you host email on the VPS, set authentication correctly: Email authentication setup tutorial.

Step 13 — Final sync, quick database refresh, and go-live

This is the only part that needs a clock.

Work the steps in order and don’t improvise.

13.1 Put the old site in maintenance mode

Enable your maintenance plugin or block writes (for stores, stop checkout).

This minimizes last-minute data drift.

13.2 Run a second rsync (fast delta copy)

Run rsync again. It should transfer only the deltas:

sudo rsync -aHAX --delete --info=progress2 \
  -e "ssh -p 22" \
  olduser@OLD_SERVER_IP:/var/www/example.com/public/ \
  /var/www/example.com/public/

Why --delete: It removes files deleted on the old host so the new server matches the source.

Don’t use it unless you’re 100% confident your source path is correct.

13.3 Refresh the database (if the site changed during prep)

If anything changed since your first dump, do one last dump/import now.

For busy stores, keep the freeze window tight and export right before you flip DNS.

13.4 Switch DNS A/AAAA records

Update:

  • A record for example.com → NEW_VPS_IP
  • A record for www (or CNAME to root) → consistent target
  • AAAA if you serve IPv6 (only if the new VPS has IPv6 configured)

Verify from a few networks:

dig +short example.com A
dig +short www.example.com A

Watch the new VPS logs to confirm traffic is arriving:

sudo tail -f /var/log/nginx/example.com.access.log

Step 14 — Post-migration verification (the 30-minute checklist)

Once traffic starts landing on the new server, run this checklist.

It covers the failures users notice first.

  • HTTP status: Confirm https://example.com returns 200 and redirects are correct.
  • Admin login: Log in and save permalinks once (Settings → Permalinks → Save).
  • Background tasks: Ensure WP‑Cron isn’t silently failing. If scheduled posts have been flaky, fix it properly with a real cron job: WordPress cron troubleshooting tutorial.
  • Email from WordPress: Test password resets and form notifications. If delivery is inconsistent, route through a relay: Email relay setup tutorial.
  • Performance: Check TTFB and page load times. Measure first, then tune.
  • Backups: Turn on backups immediately on the new server and run a restore test this week.

Step 15 — Rollback plan (you’ll sleep better)

Rollback should be boring too.

Your lever is DNS, so keep it easy to flip back.

  • Keep the old hosting active for at least 48–72 hours.
  • Don’t cancel the old plan until you’re sure email, cron, and third-party integrations are stable.
  • If things go wrong, point A records back to the old IP (your lowered TTL makes this fast).

If you want a disciplined “prove it works” routine, run a restore drill after the move: VPS restore drill tutorial.

Summary: your repeatable WordPress move workflow

This WordPress migration tutorial keeps the process intentionally plain.

Build a clean VPS, copy files with rsync, import the database, and test via hosts-file.

Then do a short final sync and DNS cutover. After the switch, verify email, cron, SSL renewals, and backups.

If you want a stable target for migrations—without fighting noisy neighbors—start with a HostMyCode VPS.

If you’d rather hand off ongoing maintenance and hardening, choose managed VPS hosting and keep your focus on the site.

Planning a WordPress move and want a VPS that stays predictable during cutover? HostMyCode offers HostMyCode VPS plans for hands-on admins, and managed VPS hosting if you want help keeping the server patched, monitored, and stable.

FAQ

How long does a WordPress migration usually take?

For a typical 2–10 GB site, the initial copy often takes 10–60 minutes depending on disk and network speed.

The final sync is usually a few minutes. DNS propagation depends on TTL; set it to 300 seconds ahead of time.

Should I migrate by cloning the whole disk image instead?

Disk images can be fast, but they also copy old problems: misconfigurations, cruft, and outdated packages.

File + DB migration is slower but cleaner, and it’s easier to verify step-by-step.

What if my site breaks after the move but only for some visitors?

That’s usually DNS caching or mixed traffic during propagation.

Keep the old server up, ensure both servers have the same content during the cutover window, and confirm your TTL was lowered in advance.

Do I need to change anything inside WordPress if the domain stays the same?

Usually no. If the domain remains identical, you’ll mostly adjust server config, SSL, and database credentials.

Only run search/replace if you’re changing domain or moving from HTTP to HTTPS and your content is hard-coded.

What’s the safest way to test before DNS changes?

Use a local hosts-file override pointing the domain to the new VPS IP.

It lets you test the real domain, real URLs, and real SSL behavior before you change public DNS.

WordPress Migration Tutorial (2026): Move a Site to a New VPS with Rsync, Database Export, and a Low‑Downtime DNS Cutover | HostMyCode