How to Migrate a WordPress Site: Zero-Downtime Tutorial (2026)
How to migrate a WordPress site without breaking it: back up everything first, transfer files with rsync or SFTP, import the database with mysqldump, fix the URLs with WP-CLI search-replace, and lower DNS TTL before you flip the A record. Total elapsed time is one to three days. Visitor downtime should be zero. I have run this exact playbook on more than 200 client migrations across the last decade. The only thing that breaks is the old hosting bill.
This tutorial walks through every step with real Bash, WP-CLI, and rsync commands. The example assumes you are moving from a cPanel-based shared host to a VPS or managed WordPress host with SSH access. The principles work for any WordPress migration; the commands work directly on managed hosts that expose SSH (Kinsta, Cloudways, WP Engine, Liquid Web, A2 Hosting).

Three ways to migrate a WordPress site
Before the manual workflow, know which path fits your case. There are three legitimate ways to migrate a WordPress site, and the right one depends on how much control you have over the new host and how big the site is.
- Free team-handled migration from the new host. Easiest path; works for nearly any size. Kinsta offers free unlimited migrations on every plan, handled within 24 to 48 hours. WP Engine and Nexcess include one free migration each.
- Migration plugin path like All-in-One WP Migration, Duplicator, or Migrate Guru. Free for sites under 512 MB to 1 GB. Quick to run, works with any host that allows plugin installs.
- Manual SSH migration with rsync, mysqldump, and WP-CLI. The path this tutorial covers. Works for any size, gives you total control, requires command-line comfort. Use it when the migration plugin times out or you need surgical control over the move.
For most readers running a small to medium WordPress site, the team-handled path is the right choice. The manual path below is for sites where the plugin path fails (over 1 GB, complex multisite, custom database tables) or for engineers who want to know exactly what’s happening at every step. Either way, the prerequisites are the same.
Pre-migration checklist: what to do before you start
Five minutes of pre-migration prep saves hours of debugging later. Run through this list before you touch a single file.
- Update WordPress core, themes, and plugins to the latest stable versions on the source site
- Disable WordPress maintenance mode plugins; they will block migration
- Clear all cache plugins (WP Super Cache, W3 Total Cache, LiteSpeed Cache) to avoid migrating stale HTML
- Document your active plugins, custom cron jobs, and any non-default .htaccess rules
- Verify SSH access to the new host and confirm PHP version matches (or is newer than) the source
- Lower your domain’s DNS TTL to 300 seconds at least 24 hours before cutover
- Generate a fresh full backup; download it locally as a safety net

The downloadable checklist above covers source-side prep, target-host prep, post-migration smoke tests, and day-7 verification. Print it before any migration; tick every box before you flip DNS.
Step 1: Back up the source WordPress site
The first rule of any WordPress migration: backups before changes. SSH into the source host, archive wp-content and wp-config.php, dump the database, and download both files locally. If anything goes wrong in the next two hours, you can restore the source site from these archives.
# SSH into the source host
ssh [email protected]
# Move into the document root
cd ~/public_html
# Archive WordPress files (excluding wp-content/cache for size)
tar --exclude='wp-content/cache' --exclude='wp-content/uploads/cache' \
-czf /tmp/wp-files-$(date +%Y%m%d).tar.gz .
# Read DB credentials from wp-config.php
DB_NAME=$(grep DB_NAME wp-config.php | cut -d "'" -f 4)
DB_USER=$(grep DB_USER wp-config.php | cut -d "'" -f 4)
DB_PASS=$(grep DB_PASSWORD wp-config.php | cut -d "'" -f 4)
DB_HOST=$(grep DB_HOST wp-config.php | cut -d "'" -f 4)
# Dump the database
mysqldump --single-transaction --quick --add-drop-table \
-h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" \
| gzip > /tmp/wp-db-$(date +%Y%m%d).sql.gz
# Verify file sizes are reasonable
ls -lh /tmp/wp-*-$(date +%Y%m%d)*The mysqldump flags matter. --single-transaction creates a consistent snapshot without locking tables (essential for live sites). --quick streams rows instead of buffering in memory (handles large databases). --add-drop-table includes DROP TABLE statements so the import on the new host is idempotent.
Verify the database dump opens and contains real data before you proceed. Run zcat /tmp/wp-db-*.sql.gz | head -100 and confirm you see CREATE TABLE statements for wp_posts, wp_postmeta, wp_options. An empty or truncated dump is the most common silent failure mode for WordPress migrations.
Step 2: Provision the new host with matching versions
Set up WordPress on the new host. The PHP version on the new host must match or be newer than the source. The MySQL or MariaDB version must be compatible with your dump. Most managed WordPress hosts ship PHP 8.1 to 8.4 and MariaDB 10.6+ which covers nearly any modern source.
# SSH into the new host
ssh [email protected]
# Confirm PHP version
php -v
# Should report PHP 8.1 or newer
# Confirm MySQL/MariaDB
mysql --version
# Create the empty database (skip if managed host did it for you)
mysql -u root -p << EOF
CREATE DATABASE wp_target CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'strong_password_here';
GRANT ALL PRIVILEGES ON wp_target.* TO 'wp_user'@'localhost';
FLUSH PRIVILEGES;
EOFIf the new host already has a fresh WordPress install (most managed WP hosts do), drop the existing wp_posts, wp_options, etc. tables before importing your dump. The --add-drop-table flag in your mysqldump handles this automatically when you import.
Step 3: Transfer files with rsync
rsync is the right tool for transferring WordPress files between hosts. It is faster than SFTP for many small files (which is exactly what wp-content/uploads is), supports resume on interruption, and handles permissions correctly. Run rsync from the new host, pulling from the source.
# From the new host, pull wp-content from source
rsync -avz --progress \
--exclude 'wp-content/cache/' \
--exclude 'wp-content/uploads/cache/' \
--exclude '.git/' \
-e "ssh -p 22" \
[email protected]:~/public_html/wp-content/ \
~/public_html/wp-content/
# Pull wp-config.php (will edit DB creds in Step 4)
scp [email protected]:~/public_html/wp-config.php \
~/public_html/wp-config.php.source
# Pull custom .htaccess if you use any custom rules
scp [email protected]:~/public_html/.htaccess \
~/public_html/.htaccess.sourceFor large media libraries (10+ GB of uploads), this can take 1 to 6 hours depending on bandwidth. rsync’s --progress flag shows transfer speed live. If it gets interrupted, re-run the same command and rsync will skip already-transferred files. The -z flag enables compression in transit; for already-compressed files like JPGs, drop it for a small speedup.
Step 4: Import the database with mysqldump and fix URLs
Transfer the database dump, import it on the new host, and run a search-replace to fix the site URL. WP-CLI’s search-replace is the right tool because it correctly handles serialized PHP data (which a plain SQL UPDATE will corrupt for any plugin storing arrays).
# Copy the dump from source to new host
scp [email protected]:/tmp/wp-db-*.sql.gz \
~/wp-db.sql.gz
# Decompress
gunzip wp-db.sql.gz
# Import into the new database
mysql -u wp_user -p wp_target < wp-db.sql
# Update wp-config.php with new DB credentials
sed -i "s/define( 'DB_NAME', .*/define( 'DB_NAME', 'wp_target' );/" ~/public_html/wp-config.php
sed -i "s/define( 'DB_USER', .*/define( 'DB_USER', 'wp_user' );/" ~/public_html/wp-config.php
sed -i "s/define( 'DB_PASSWORD', .*/define( 'DB_PASSWORD', 'strong_password_here' );/" ~/public_html/wp-config.php
# Fix the site URL with wp-cli (only run if the URL is changing)
cd ~/public_html
wp search-replace 'https://oldsite.com' 'https://newsite.com' \
--skip-columns=guid \
--recurse-objects \
--all-tables-with-prefix
# If the domain stays the same but you're on a temp URL, search-replace
# from the temp URL back to production after DNS cutover.The --skip-columns=guid flag is critical. The guid column in wp_posts should never be changed because RSS feeds, WordPress’s own deduplication logic, and some legacy plugins use guid as a stable identifier. Changing it breaks RSS subscriptions silently.
The --recurse-objects flag tells WP-CLI to deserialize, search, replace, and re-serialize PHP objects. Without it, plugins that store arrays in wp_options (most page builders, most cache plugins, some SEO plugins) will have broken serialized data and silent fatals.
Step 5: Test the migrated site on a staging URL
Before flipping DNS, verify the migrated copy works. Most managed hosts give you a staging URL (like your-site.kinsta.cloud or your-site.wpengine.com) that bypasses DNS. For self-managed VPS, edit your local /etc/hosts file to point your domain at the new server’s IP for testing without affecting other visitors.
# On macOS/Linux, edit /etc/hosts
sudo nano /etc/hosts
# Add a line pointing your domain at the new server IP
123.45.67.89 yoursite.com www.yoursite.com
# Flush DNS cache
# macOS:
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
# Linux:
sudo systemctl restart systemd-resolvedNow your browser will hit the new server when you visit yoursite.com, while every other visitor still hits the old host. Click through every page type: homepage, a blog post, a category archive, the contact form, the search results, /wp-admin login. Anything that breaks here will break for real visitors after DNS cutover.
- Homepage loads with no PHP errors in the response
- A random blog post loads with images and styling intact
- /wp-admin login works and shows the dashboard
- Permalink structure works (no 404s on post URLs)
- Image uploads via Media Library save to the new server
- Contact form submission works (test with a real submission)
- Search returns expected results
- Sitemap.xml renders correctly
- Email-out tests work (password reset, comment notification)
Step 6: Cut over DNS to the new host
You should have lowered the DNS TTL to 300 seconds 24 hours ago (in the pre-migration checklist). Now update the A record at your DNS provider to point at the new server’s IP address. Propagation typically takes 5 to 60 minutes globally, with most regions seeing the change in under 15 minutes.
# Verify DNS propagation from your local machine
dig yoursite.com +short
# Verify from multiple geographic resolvers
dig @8.8.8.8 yoursite.com +short # Google DNS (US)
dig @1.1.1.1 yoursite.com +short # Cloudflare DNS (global)
dig @208.67.222.222 yoursite.com +short # OpenDNS
# Once you see the new IP everywhere, restore /etc/hosts
sudo nano /etc/hosts # remove your testing lineIf you’re using Cloudflare or another DNS proxy, the cutover is even faster because Cloudflare’s edge already has the new origin IP cached the moment you save the change. Some managed hosts (Kinsta, WP Engine) auto-issue a Let’s Encrypt SSL certificate on their server within minutes of detecting the new DNS pointing at them. Verify the SSL works before celebrating.
Step 7: Monitor and decommission the old host
Keep the old host live for 14 days as a fallback. If anything goes wrong on the new host you can revert DNS in seconds. After 14 days of stable operation on the new host, with backups running and Search Console showing normal crawl, you can cancel the old host plan.
- Day 1: DNS cutover complete, smoke test passes, SSL valid
- Day 2: Confirm new-host backup ran successfully overnight
- Day 3-7: Monitor Google Search Console for crawl errors and indexing changes
- Day 7: Verify analytics traffic matches your historical baseline within 5 percent
- Day 14: Confirm zero issues for 7 consecutive days, then cancel old host
- Day 14: Save the old host’s final backup locally as a permanent archive
Most migration “failures” actually surface in week two when something breaks that wasn’t caught in the smoke test. A broken cron job that wasn’t migrated. An email-sending plugin still pointing at the old server. A subdomain that still has its A record on the old host. The 14-day fallback window covers all of these.
Common WordPress migration problems and fixes
Three problems cause 90 percent of failed WordPress migrations. Here’s how to diagnose and fix each.
Mixed content warnings after the move. Hard-coded HTTP image URLs in posts are still HTTP after the migration. Fix with another pass of wp search-replace 'http://yoursite.com' 'https://yoursite.com' --skip-columns=guid. Browser console will show every offending URL.
500 errors on /wp-admin or specific pages. Usually a plugin or theme that depends on a different PHP extension on the old host. Check error logs with tail -f ~/logs/php-errors.log. The most common culprits are plugins requiring SOAP, mcrypt (deprecated since PHP 7.2), or specific PHP-Imagick versions.
Broken permalinks (404 on every post except the homepage). Either .htaccess didn’t transfer (Apache hosts) or the rewrite rules need to be flushed. Fix: visit Settings → Permalinks in /wp-admin and click “Save Changes” — that re-flushes the rewrite rules. On Nginx-based managed hosts the rewrite rules are server-config, not .htaccess; the host’s WordPress recipe handles them automatically.
For deeper hosting context, see my best managed WordPress hosting guide and the best VPS hosting roundup. Both cover the destination platforms most readers migrate to.
Migrating WordPress multisite networks
WordPress multisite migration follows the same pattern with two extra considerations. First, the wp_blogs and wp_site tables contain network-wide configuration that must transfer along with the per-site tables. Second, the new host has to support multisite at the plan tier you are buying. Kinsta gates multisite to Pro plans and above ($70/month). WP Engine allows multisite on Startup. Most managed hosts support either subdomain or subdirectory installs but you have to specify which when provisioning.
The search-replace step needs an extra flag for multisite: wp search-replace --network when running on the network’s wp-cli context. Otherwise the replacement only touches the main site’s tables and leaves subsites with the old URLs.
# Multisite-aware search-replace
cd ~/public_html
wp search-replace 'https://oldsite.com' 'https://newsite.com' \
--skip-columns=guid \
--recurse-objects \
--network \
--all-tables-with-prefix
# Verify each subsite is reachable
wp site list --field=url
# Then curl each URL to confirm it returns 200Special cases: WooCommerce and large media libraries
WooCommerce stores need maintenance mode during the database cutover window. Otherwise orders placed on the old host after your dump but before DNS cutover will be lost. Use WooCommerce’s built-in maintenance mode (WP-CLI: wp maintenance-mode activate) for 30 to 60 minutes during the actual cutover.
Large media libraries (10+ GB of uploads) take hours to rsync. The right strategy is two-pass migration. First pass: rsync everything to the new host while the source is still live. Second pass: a final delta rsync immediately before DNS cutover, capturing only the files modified in the last few hours. The second pass takes minutes, not hours, because rsync only transfers changed files.
# First pass (run a day or two before cutover)
rsync -avz --progress \
-e "ssh -p 22" \
[email protected]:~/public_html/wp-content/uploads/ \
~/public_html/wp-content/uploads/
# Second pass — delta sync immediately before DNS cutover
rsync -avz --progress --delete \
-e "ssh -p 22" \
[email protected]:~/public_html/wp-content/uploads/ \
~/public_html/wp-content/uploads/
# --delete removes files on target that are gone from source.
# Use with caution; remove the flag if you've added files manually on target.When to use a managed migration service instead
The manual workflow above is the right choice when you need surgical control or your site is too large for plugin-based migration. For most readers, paying the new host to handle the migration is faster and lower-risk. Costs in 2026:
- Kinsta — free unlimited migrations on every plan, handled within 24 to 48 hours
- WP Engine — one free Standard Migration per account, $79–$249 for additional migrations
- Cloudways — free migration plugin or paid white-glove migration ($25/site)
- Liquid Web — free migrations included on all managed WordPress plans
- Nexcess — free migration on all WordPress and WooCommerce plans
- BlogVault Migrate Guru — free standalone plugin works for any host pair
For a single-site migration of a typical WordPress blog or business site, the time saved by letting Kinsta handle it (about 4 to 6 hours of your time) is worth more than the price difference vs other hosts that don’t include free unlimited migrations.
Post-migration optimization checklist
The migration is the start, not the finish. The first 14 days on the new host are the right time to enable optimizations the old host did not support and validate that performance has actually improved.
- Enable Redis or Memcached object caching if the new host supports it
- Configure server-level page cache (LiteSpeed Cache, Nginx FastCGI, or Kinsta’s built-in cache)
- Enable Cloudflare Enterprise edge cache (bundled on Kinsta and Rocket.net)
- Verify automatic daily backups are running and downloadable
- Set up uptime monitoring with UptimeRobot or BetterStack
- Submit the new sitemap to Google Search Console and Bing Webmaster Tools
- Run PageSpeed Insights and confirm Core Web Vitals improved vs the old baseline
- Configure 404 monitoring; broken redirects are the most common silent migration regression
For deeper performance work post-migration, see my fastest web hosting guide for the speed stack and the best managed WordPress hosting roundup for platform-specific optimization tips. Each managed host has small differences in how object cache and edge cache are enabled.
Frequently asked questions about migrating WordPress sites
How long does it take to migrate a WordPress site?
A typical WordPress site migration takes 1 to 3 days end to end. The actual transfer work is 2 to 6 hours. The bulk of elapsed time is the 24-hour DNS TTL lowering before cutover and the 14-day stability monitoring after cutover. Visitor downtime should be zero.
Will my SEO rankings drop after a WordPress migration?
Not if the migration is done correctly. Keep URLs identical, preserve permalink structure, set up 301 redirects for any URL changes, submit the new sitemap to Search Console, and the rankings stay stable. Most well-executed migrations show no measurable change in organic traffic.
Can I migrate a WordPress site without losing data?
Yes. Always take a full backup before starting. Keep the old host live for 14 days after cutover as a fallback. Use rsync (resume-friendly) for files and mysqldump –single-transaction for the database to avoid corruption. The most common data loss is from skipping the verification step on the database dump.
What is the best free WordPress migration plugin?
All-in-One WP Migration is the most popular for sites under 512 MB. Duplicator handles larger sites with the free Lite version. Migrate Guru by BlogVault is the best free plugin for sites with multiple GB of media. For sites over 5 GB, use the manual SSH path or pay for the host’s migration team.
Do I need to update the wp-config.php during migration?
Yes. The DB_NAME, DB_USER, DB_PASSWORD, and DB_HOST values in wp-config.php must match the new database. Also regenerate the AUTH_KEY, SECURE_AUTH_KEY, and other unique keys via the WordPress secret-key API at api.wordpress.org/secret-key/1.1/salt/.
Why does WP-CLI search-replace need –skip-columns=guid?
The guid column is a permanent unique identifier for posts that RSS feeds, WordPress’s deduplication logic, and some plugins use as a stable reference. Changing it breaks RSS subscriptions and can cause subscribers to re-receive every post as new. Always preserve guid values across migrations.
Should I lower DNS TTL before a WordPress migration?
Yes. Lower the TTL to 300 seconds at least 24 hours before cutover. This means DNS resolvers cache the old IP for only 5 minutes, so when you flip to the new IP, propagation completes globally within 5 to 15 minutes instead of 24 to 48 hours.
Can I migrate a WooCommerce store the same way?
Yes, with one extra step. Put the WooCommerce store into maintenance mode (Tools → Site Health) for the brief cutover window so no orders are placed on the old host after the database snapshot is taken. Run the migration, then turn maintenance mode off. Total order-blocking window: 30 to 60 minutes.
Final notes on migrating WordPress sites cleanly
The hardest part of any WordPress migration is psychological. The actual technical workflow is well-known, well-documented, and reliable when run carefully. Most readers should use their new host’s free team-handled migration; it’s the lowest-risk path and costs nothing on Kinsta or one-time on WP Engine. The manual SSH path covered above is for cases where the plugin or team path can’t handle the size or complexity of your site.
Take backups before changes. Lower DNS TTL before cutover. Test on staging before flipping the A record. Keep the old host alive for 14 days as a fallback. Print the migration checklist and tick every box. The combination of these habits is the difference between a clean WordPress migration and a stressful weekend trying to figure out why /wp-admin returns a 500 error. For broader hosting strategy, see my best web hosting guide and the fastest web hosting roundup.