Back to tutorials
Tutorial

WordPress migration tutorial (2026): Move from Shared Hosting to a VPS Without Downtime, Broken SSL, or Lost Email

WordPress migration tutorial for 2026: move from shared hosting to a VPS with staged DNS cutover, SSL checks, and email-safe steps.

By Anurag Singh
Updated on Oct 07, 2026
Category: Tutorial
Share article
WordPress migration tutorial (2026): Move from Shared Hosting to a VPS Without Downtime, Broken SSL, or Lost Email

A clean migration is less about copying files and more about managing state. That state includes DNS, HTTPS, background jobs, and mail routing. This WordPress migration tutorial uses a staged move from shared hosting to a VPS. The goal is simple: keep the site reachable, protect SEO signals, and avoid “SSL broke” or “contact form stopped emailing” surprises.

You’ll build the new server in parallel. Then you’ll sync changes more than once, rehearse the cutover, and switch DNS on a tight schedule. The steps below assume a typical WordPress site (or WooCommerce) moving to Ubuntu 24.04 LTS on a VPS with Nginx + PHP-FPM and Let’s Encrypt.

What you’ll migrate (and what people forget)

  • Web files: wp-content is the real payload; core can be reinstalled.
  • Database: posts, users, settings, and WooCommerce orders live here.
  • DNS: A/AAAA records, CNAMEs, and TTL strategy for cutover.
  • SSL/TLS: certificates, renewals, redirects, HSTS, and mixed content.
  • Email dependencies: MX records, SPF/DKIM/DMARC alignment, outbound SMTP, and contact forms.
  • Cron/queues: WP-Cron or real cron, scheduled tasks, and cache warmups.

If you want a managed baseline on the new server (patching, security defaults, and help during cutover), managed VPS hosting takes a lot of risk off the table.

If you prefer to run the steps yourself, a HostMyCode VPS gives you a clean staging environment and a controlled DNS switch.

Prerequisites and a practical plan

Before you touch production, pick a migration window. Define “done” in plain terms: the homepage loads, checkout works, admin login works, forms deliver mail, and search console shows no new crawl failures.

Then make sure you have:

  • Domain registrar/DNS access (or Cloudflare access)
  • Current hosting credentials (SFTP/FTP, SSH if available, phpMyAdmin)
  • WordPress admin access
  • New VPS IP address (IPv4; IPv6 optional)

Downtime strategy: Drop DNS TTL, build the VPS in parallel, do a final sync, then cut over. If TTL is low and you time the freeze well, most sites only see a short “split traffic” period while caches expire.

Step 1 — Lower DNS TTL 24 hours ahead

TTL controls how long resolvers cache your records. If it’s high (3600–86400 seconds), you’ll wait longer than you expect. You also won’t control where users land during the switch.

  1. Find your current A/AAAA records for the site (root @ and www).
  2. Set TTL to 300 seconds (5 minutes) at least 24 hours before cutover.

If you use Cloudflare DNS, follow this guide to avoid common traps like proxy mode hiding origin issues: Cloudflare DNS setup guide tutorial.

Quick diagnostic: confirm the TTL change is actually live.

dig +nocmd example.com A +noall +answer

Step 2 — Prepare the VPS (Ubuntu 24.04) for WordPress hosting

Keep the stack boring during the move. If you’re migrating for performance, resist the urge to “improve everything” mid-cutover. Get stable first. Then tune.

Install Nginx, PHP-FPM, and common extensions

sudo apt update
sudo apt -y install nginx
sudo apt -y install php8.3-fpm php8.3-cli php8.3-mysql php8.3-curl php8.3-gd php8.3-xml php8.3-mbstring php8.3-zip php8.3-intl
sudo systemctl enable --now nginx php8.3-fpm

PHP 8.3 is a safe default for WordPress in 2026. Use something older only if a must-have plugin forces it.

On a VPS, you get to choose the runtime. You can also test that choice before you switch DNS.

Create a web root and a deploy user

sudo adduser --disabled-password --gecos "" deploy
sudo mkdir -p /var/www/example.com/public
sudo chown -R deploy:deploy /var/www/example.com

Base firewall rules (keep it boring)

Open only what you need. If you haven’t set this up yet, use: VPS firewall setup guide tutorial.

Typical UFW rules:

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

Step 3 — Create the new site vhost (HTTP first)

Stand the site up on plain HTTP first. Add HTTPS after you can reach the hostnames safely and know the app boots cleanly.

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

Example Nginx server block (minimal, WordPress-friendly):

server {
  listen 80;
  listen [::]:80;

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

  client_max_body_size 64m;

  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 ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|webp)$ {
    expires 7d;
    add_header Cache-Control "public";
    try_files $uri =404;
  }
}
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Step 4 — Copy WordPress files (initial sync)

On shared hosting, you usually land in one of two situations:

  • You have SSH + rsync (best case).
  • You only have SFTP/FTP and a control panel file manager.

Option A: rsync over SSH (recommended)

From your local machine or from the new VPS (if the shared host allows inbound SSH), sync the web root. Prioritize wp-content, plus wp-config.php.

rsync -avz --delete \
  user@oldhost:/home/user/public_html/ \
  /var/www/example.com/public/

Pitfall: Don’t migrate cache directories that your plugin rebuilds anyway. You’ll waste time. You can also carry over stale paths.

It’s usually safe to exclude:

--exclude 'wp-content/cache/' --exclude 'wp-content/wflogs/'

Option B: SFTP pull using lftp (works even when rsync isn’t available)

sudo apt -y install lftp
lftp -e "set sftp:auto-confirm yes; mirror -e /public_html /var/www/example.com/public; bye" \
  -u USERNAME sftp://oldhost

Step 5 — Export and import the database

On shared hosting, phpMyAdmin is common. If you have SSH access, mysqldump is faster. It’s also easy to repeat during final sync.

Export (old host)

mysqldump --single-transaction --routines --triggers \
  -uOLD_DB_USER -p OLD_DB_NAME > db.sql

If your shared host blocks mysqldump, export from phpMyAdmin as SQL and download it.

Create DB + user (new VPS)

If you already run MySQL/MariaDB on the VPS, create a database and user. (If you don’t, install the server package first.)

sudo apt -y install mariadb-server
sudo systemctl enable --now mariadb
sudo mysql
CREATE DATABASE wp_example DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'USE-A-LONG-RANDOM-PASSWORD';
GRANT ALL PRIVILEGES ON wp_example.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Import (new VPS)

mysql -uwpuser -p wp_example < db.sql

Step 6 — Update wp-config.php and verify permissions

Edit /var/www/example.com/public/wp-config.php so it matches the new database credentials.

define('DB_NAME', 'wp_example');
define('DB_USER', 'wpuser');
define('DB_PASSWORD', 'USE-A-LONG-RANDOM-PASSWORD');
define('DB_HOST', 'localhost');

Set conservative ownership. For a simple VPS setup where Nginx runs as www-data:

sudo chown -R deploy: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 {} \;

If your workflow needs WordPress to write files (uploads, plugin installs), make sure wp-content/uploads stays writable by the web group:

sudo chgrp -R www-data /var/www/example.com/public/wp-content/uploads
sudo chmod -R 775 /var/www/example.com/public/wp-content/uploads

Step 7 — Test the site on the new VPS without changing public DNS

You want your own browser hitting the new server while the public still hits the old one. That lets you catch issues without exposing users to them.

Method A: hosts file override (fast)

On your local machine, map the domain to the new VPS IP. Example:

203.0.113.10 example.com www.example.com

Load the site and log in to /wp-admin. This is where missing PHP extensions, broken paths, and redirect loops show up.

If you hit redirect loops after enabling HTTPS later, keep this troubleshooting guide handy: HTTPS redirect troubleshooting tutorial.

Method B: temporary subdomain (cleaner for team testing)

Create staging.example.com pointing to the new VPS. This avoids local overrides. It also lets non-technical reviewers test checkout, forms, and admin workflows with a normal URL.

Step 8 — Install Let’s Encrypt SSL on the new VPS

After you can reach the new server for the hostname (hosts override or staging DNS), issue certificates. Use Certbot with the Nginx plugin:

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

Certbot will offer an HTTP→HTTPS redirect. For WooCommerce, accept it. Then re-test login, cart, and checkout. Those are the first places bad redirects show up.

For common HTTPS problems (wrong chain, renew failures, or mixed content), use: SSL Certificate Setup Guide Tutorial.

Step 9 — Fix WordPress URLs (only if needed)

If the domain stays the same, you often don’t need to touch URLs. Still, page builders and older plugins sometimes hardcode links. That can lead to mixed content or odd redirects.

Check current site URLs:

wp option get home --path=/var/www/example.com/public
wp option get siteurl --path=/var/www/example.com/public

If you don’t have WP-CLI installed:

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

If you need to rewrite URLs (for example, you tested on a temporary domain and are switching back), use a search-replace that handles serialized data correctly:

wp search-replace 'http://staging.example.com' 'https://example.com' \
  --all-tables --precise --recurse-objects \
  --path=/var/www/example.com/public

Warning: Avoid running blind SQL replace statements on WordPress unless you understand serialization. WP-CLI is the safer tool.

Step 10 — Make email-safe decisions (contact forms and outbound mail)

Plenty of migrations “succeed” and quietly break email. Password resets never arrive. WooCommerce order messages disappear. Deliverability can also drop when SPF/rDNS no longer matches your sending IP.

Pick the model you’re actually using:

  • Keep email hosted elsewhere (Google Workspace, Microsoft 365, or a dedicated mail provider). In this case, your MX records should not point to the new VPS.
  • Move email to the VPS. Then plan deliverability properly: rDNS, SPF, DKIM, DMARC, and sensible rate limits.

If you’re not migrating mail on purpose, double-check that MX records stay unchanged during the web cutover. Use: MX Record Setup Tutorial.

If you are moving mail and want deliverability that holds up, start here: Email Deliverability Setup Guide Tutorial.

Also make sure your new IP has reverse DNS set correctly: Reverse DNS Setup Tutorial.

Step 11 — Do a final content sync and freeze changes

Your first file/database copy is already out of date. Comments, orders, uploads, and plugin settings can change while you’re building the new box.

  1. Freeze edits: put the old site into brief maintenance mode, or at least pause publishing and store changes.
  2. Final file sync: run rsync again to capture fresh uploads.
  3. Final DB export/import: dump again and re-import to the new VPS.

Example rsync (repeat of Step 4):

rsync -avz --delete \
  --exclude 'wp-content/cache/' --exclude 'wp-content/wflogs/' \
  user@oldhost:/home/user/public_html/ \
  /var/www/example.com/public/

WooCommerce note: If orders arrive nonstop, keep the freeze window short. Do the final sync right before the DNS switch. Then validate payment webhooks right after.

Step 12 — Cut over DNS (and verify propagation)

Update A/AAAA records for @ and www to the new VPS IP. Leave TTL at 300 for a few hours after cutover. That way you can react quickly if something unexpected shows up.

Watch propagation from multiple resolvers:

dig @1.1.1.1 example.com A +short
dig @8.8.8.8 example.com A +short
dig @9.9.9.9 example.com A +short

Also confirm requests are landing on the new server. One simple trick is adding a temporary header to your Nginx vhost:

add_header X-Migrated-To "new-vps" always;

Reload Nginx, then check the response headers in dev tools or with curl:

curl -I https://example.com | grep -i x-migrated

Step 13 — Post-cutover checklist (don’t skip these)

  • Permalinks: in WP admin, re-save permalinks (it forces rewrite rule refresh).
  • HTTPS health: no redirect loops, no mixed content warnings.
  • Uploads: upload a test image; confirm permissions on wp-content/uploads.
  • Forms: submit a contact form and confirm delivery.
  • Transactions: if WooCommerce, run a real checkout test (even a low-value one) and confirm order email.
  • Performance: check TTFB and page load time; make sure PHP-FPM is active and not falling back.
  • Error logs: watch /var/log/nginx/example.com.error.log and PHP-FPM logs for 30 minutes.

Quick diagnostic commands:

sudo tail -n 100 /var/log/nginx/example.com.error.log
sudo journalctl -u php8.3-fpm --since "30 minutes ago" --no-pager

Step 14 — Turn the old hosting account into a safety net (temporary)

Don’t cancel shared hosting immediately. Keep it for at least 7–14 days as rollback insurance. This also catches the last stubborn DNS caches.

  • Keep the old site in maintenance mode (or a static “moved” page) to avoid split-brain edits.
  • Keep backups from the old host until you’ve proven restores from the new environment.

If you want an actual restore-test routine (not just “we have backups”), use: VPS Backup Verification Tutorial.

Common migration failures and how to fix them fast

Redirect loop after enabling HTTPS

  • Confirm WordPress home and siteurl use https://.
  • Check for conflicting redirects (Cloudflare “Always Use HTTPS” + Nginx redirect + plugin redirect).

Use the dedicated fix flow: HTTPS Redirect Troubleshooting Tutorial.

502/504 errors under load

  • Check PHP-FPM status and logs.
  • Confirm you didn’t set pm.max_children too low for traffic.
  • Make sure swap isn’t thrashing (common on tiny VPS sizes).

If the VPS feels slow after cutover, follow a structured triage path: VPS Performance Troubleshooting Tutorial.

Media missing or uploads fail

  • Permissions on wp-content/uploads are wrong.
  • The final rsync didn’t include new uploads.

Emails not sending from forms

  • Outbound mail blocked by provider policy, or your server lacks proper DNS alignment.
  • Your form plugin relies on PHP mail() which may not be configured.

Start with DNS and deliverability checks: Email deliverability troubleshooting tutorial.

Practical hardening after the move (30-minute baseline)

After traffic is stable, spend 30 minutes tightening the basics. This cuts down brute-force noise. It also limits blast radius if something goes wrong later.

  • Disable SSH password logins; use keys only.
  • Keep the OS patched; enable unattended security upgrades.
  • Install a WAF plugin in WordPress only after performance is stable (avoid changing too much during the move).

For a safe VPS baseline hardening flow, follow: Server Hardening Tutorial.

Summary: the repeatable migration flow

  1. Lower DNS TTL a day ahead.
  2. Build the new VPS stack and vhost on HTTP.
  3. Initial file + DB sync.
  4. Test via hosts override or staging subdomain.
  5. Install SSL and verify redirects.
  6. Final sync + brief change freeze.
  7. Flip DNS and verify with multiple resolvers.
  8. Run post-cutover checks (forms, checkout, logs).
  9. Keep old host briefly as a rollback option.

If you want the move to feel routine instead of tense, run WordPress on infrastructure you can actually size and observe. A HostMyCode VPS gives you isolated resources, control over PHP versions, and clearer signals when something spikes.

If you’re planning a shared-to-VPS cutover and you want a comfortable safety margin, start with a properly sized HostMyCode VPS and stage the migration exactly as outlined above. If you’d rather not juggle OS patching, firewall rules, and service tuning during a live move, choose managed VPS hosting and let our team run the operational side while you focus on testing and sign-off.

FAQ

How long does a WordPress migration from shared hosting to a VPS take?

For a typical small-business site, plan 2–4 hours of hands-on work plus a day of low-TTL lead time. Large media libraries and WooCommerce stores take longer because final syncs must be tight.

Can I migrate without any downtime at all?

You can get close. The main risk is split traffic during DNS propagation. Lower TTL early, freeze edits briefly, do a final sync, then cut over. For stores, schedule a short maintenance window and validate payments immediately.

Do I need to move email when I move WordPress?

No. Web and mail are independent if your DNS is correct. Keep MX records pointing to your mail provider if you’re not migrating mail, and confirm SPF/DKIM remain valid for your sending system.

What’s the safest way to test the new server before switching DNS?

Use a local hosts file override for quick checks, or create a temporary subdomain like staging.example.com pointing to the new VPS so others can test without special setup.

What should I keep from the old host after migration?

Keep the account for 7–14 days, keep a copy of the last working backup, and keep notes about old DNS records. Cancel only after you’ve tested restores and confirmed email and SSL are stable.

WordPress migration tutorial (2026): Move from Shared Hosting to a VPS Without Downtime, Broken SSL, or Lost Email | HostMyCode