How to Migrate a WordPress Site Safely (Step by Step)

How to Migrate a WordPress Site Safely (Step by Step)


Moving a WordPress site sounds simple: copy the files, copy the database, point the domain at the new server. In practice, migrations are where I see some of the most stressful website incidents. Images go missing, the admin redirects back to the old domain, widgets disappear, contact forms stop sending, or an online store takes orders on the old server for a day without anyone noticing.

The good news is that almost every one of those problems is predictable, and therefore preventable. In this guide I walk through the process I use when moving WordPress and WooCommerce sites between hosts, from a local environment to live, or from one domain to another. It works whether you use WP-CLI on the command line (my preference) or a migration plugin, because the principles are the same.

The migration at a glance

A WordPress site is two things: files (WordPress core, themes, plugins and uploads) and a MySQL database (posts, pages, settings, users, orders). A migration moves both, then updates every reference to the old address, then switches visitors over. The order of these steps matters, and so does testing in between.

Eight-step WordPress migration flow: back up, copy files, move the database, replace URLs, test privately, switch DNS, monitor, and go live.1Back upFull files + databaseof the old site2Copy filesThemes, plugins,uploads, config3Move databaseExport on the old,import on the new4Replace URLsSerialization-safesearch-replace5Test privatelyHosts-file preview,before DNS changes6Switch DNSAfter lowering theTTL a day earlier7MonitorLogs, forms, email,payments, SSL8. Go liveRaise TTL, retire theold server laterSteps 1 to 5 happen before visitors notice anything. Only step 6 changes what they see.
Figure 1: A safe migration does all the risky work out of sight, then switches visitors over in one small, reversible step.

The most important idea in that diagram: visitors keep using the old site until the new one is fully tested. You never “work on the live migration”. You build a complete, verified copy, and only then flip the switch.

Before you start: inventory and backups

Ten minutes of preparation saves hours of guessing later. Before touching anything, write down:

  • PHP and MySQL versions on both servers. Moving from PHP 7.4 to PHP 8.3 is a migration and an upgrade; test for both.
  • Where email is handled. If the domain’s email (MX records) lives with the old host, changing DNS carelessly can break your inbox, not just the website.
  • Anything outside WordPress: cron jobs, custom .htaccess or Nginx rules, files in the web root that aren’t part of WordPress, server-level redirects.
  • Licensed plugins that are tied to a domain and may need re-activating.
  • The current DNS records, copied in full (A, CNAME, MX, TXT). A screenshot of the DNS zone is a cheap insurance policy.

Then take a complete backup of the old site, files and database, and download it somewhere that is neither the old server nor the new one. This backup is your rollback plan. If everything else goes wrong, you can always return to exactly where you started.

Moving the files and the database

If you have SSH access, WP-CLI makes the database part fast and reliable. On the old server, export the database and package the files:

# On the old server, from the WordPress root
wp db export backup.sql --add-drop-table

# Package everything (core, wp-content, wp-config.php, .htaccess)
tar -czf site-files.tar.gz .

Copy both files to the new server (with scp, rsync or your host’s file manager), then unpack the files and import the database:

# On the new server, inside the new web root
tar -xzf site-files.tar.gz

# Point wp-config.php at the NEW database first
wp config set DB_NAME     new_db_name
wp config set DB_USER     new_db_user
wp config set DB_PASSWORD 'new-strong-password'
wp config set DB_HOST     localhost

# Import the old data into the new database
wp db import backup.sql

Check the table prefix in wp-config.php ($table_prefix) matches the tables you just imported. A mismatch is the classic cause of the “WordPress wants to install itself again” screen after a migration.

No SSH access?

On shared hosting without SSH, a reputable migration plugin does the same job: it exports files and database into an archive and handles URL replacement on import. The rest of this guide still applies. Back up first, test before DNS, and keep the old site running until you’re sure.

Replacing URLs without breaking serialized data

WordPress stores full URLs in the database: in post content, in image references, and in settings. If the domain changes (or you move from http to https), every one of those references must be updated. This is the step that breaks the most migrations, because of how WordPress stores settings.

Many options, widgets, theme settings and plugin configurations are saved as serialized PHP data. Serialized strings record their own length. If you change the text but not the recorded length, PHP can no longer read the value, and WordPress quietly treats it as empty.

A serialized string records its length. A raw SQL REPLACE changes the URL but keeps the old length of 18, so the data can no longer be read. WP-CLI search-replace updates the length to 23, so the data stays intact.Saved in the database (serialized)s:18:"http://old.example";Raw SQL REPLACE()s:18:"https://www.example.com";Length still says 18, the string is 23.PHP can't unserialize it, so settings,widgets and menus silently vanish.wp search-replaces:23:"https://www.example.com";Length recalculated to 23.The value unserializes correctly,so nothing is lost.
Figure 2: Why a plain find-and-replace corrupts WordPress settings, and why a serialization-aware tool doesn’t.

That’s why you should never run a raw SQL REPLACE() across WordPress tables, or open the SQL file in a text editor and use find-and-replace. Use a tool that understands serialization. WP-CLI’s search-replace command unserializes each value, replaces the text, and serializes it again with the correct lengths. Always do a dry run first:

# 1. Preview: how many replacements, in which tables?
wp search-replace 'https://old-domain.com' 'https://new-domain.com' --dry-run --report-changed-only

# 2. Run it for real (--all-tables also covers tables with a custom prefix)
wp search-replace 'https://old-domain.com' 'https://new-domain.com' --all-tables --precise

# 3. Moving from http to https on the same domain?
wp search-replace 'http://example.com' 'https://example.com' --all-tables

# 4. Clear caches so nothing serves the old values
wp cache flush
wp rewrite flush

A few details I always double-check:

  • Replace the full URL, including the protocol. Replacing just old-domain can hit email addresses and unrelated text.
  • Don’t replace the GUID column on a site that is already live and has RSS subscribers. WP-CLI skips guid by default, which is the right behaviour.
  • Check the site URL options afterwards with wp option get siteurl and wp option get home.
  • Page builders such as Elementor keep their own CSS files. Regenerate them after the replacement (Elementor has a “Regenerate CSS & Data” tool for this).

Testing before anyone sees it

You can preview the new server with the real domain before changing DNS. Add a line to your computer’s hosts file that points the domain to the new server’s IP address. Your browser then loads the new server while everyone else still sees the old one.

# Windows: C:\Windows\System32\drivers\etc\hosts
# macOS / Linux: /etc/hosts
203.0.113.25   example.com www.example.com

(Use your new server’s real IP; the address above is a documentation example.) Then go through the site as a visitor and as an editor: the homepage, a few posts, images, forms, search, the login, the admin, and anything that’s specific to the business. Remove the hosts entry when you’re done, or you’ll keep seeing the new server and wonder why nobody else does.

Switching DNS with minimal downtime

When you change where your domain points, the change isn’t instant everywhere. DNS resolvers around the world cache your records for as long as the TTL (time to live) says. If your TTL is 24 hours, some visitors may keep reaching the old server for up to a day.

The fix is to lower the TTL in advance. A day or two before the move, set the TTL of the records you’ll change to something short, such as 300 seconds. Wait until the old, long TTL has expired. Then, when you switch, the change reaches almost everyone within minutes.

DNS switch timeline. Lower the TTL to 300 seconds 48 hours before; wait for the old TTL to expire; switch the A record; within minutes most visitors reach the new server; one to two days later raise the TTL again and retire the old server.Visitors reach the old serverVisitors reach the new server48 h beforeLower TTL to300 secondsOld TTL expiresResolvers nowre-check oftenSwitch DNSPoint the A recordto the new serverMinutes laterMost visitors reachthe new server1–2 days laterRaise TTL, thenretire old serverKeep the old server running until traffic to it stops. A few resolvers ignore short TTLs.
Figure 3: Lowering the TTL a day or two before the switch turns a slow, unpredictable change into one that takes minutes.

Change only the records you need to, usually the A record for the root domain and the www record. Leave MX and email-related TXT records alone unless email is moving too. If you also move the domain to new nameservers, that’s a separate, slower change; I avoid doing both on the same day.

Once the domain points to the new server, issue the SSL certificate there (most hosts do this automatically with Let’s Encrypt once DNS resolves) and confirm the site loads over https without warnings.

Extra care for WooCommerce stores

A blog can tolerate a few hours where comments land on the old server. A store can’t, because every order placed on the old server after you copied the database will be missing from the new one. For stores, I add three steps:

  1. Pick a quiet time for the final sync, based on the store’s own order history.
  2. Freeze changes on the old site during the final copy: put it in maintenance mode, or pause checkout, so no new orders arrive between the last database export and the DNS switch.
  3. Re-sync the database last. Do the full migration and testing in advance, then repeat only the database export, import and search-replace right before switching DNS. With WP-CLI, that final sync takes minutes.

Also check payment gateway webhooks (they usually point to your domain, which is fine, but some gateways restrict by IP), scheduled actions under WooCommerce → Status → Scheduled Actions, and transactional emails. Place a real test order and refund it. If you’ve built custom checkout behaviour, as described in my WooCommerce customization guide, test those paths specifically.

Launch checklist and rollback plan

This is the checklist I go through on every migration. It’s deliberately boring. Boring migrations are the goal.

WhenCheck
BeforeFull backup downloaded off-server · PHP/MySQL versions noted · DNS zone saved · TTL lowered 24–48 h ahead · email (MX) location confirmed
After importsiteurl and home correct · search-replace done (dry run first) · table prefix matches · permalinks flushed · caches cleared
Hosts-file testPages, images and menus load · login and admin work · forms send · search works · no mixed-content warnings
After DNSSSL valid · contact form emails arrive · test order and refund (stores) · cron and scheduled actions run · error log clean
A few days laterTTL raised again · Search Console shows no new errors · backups configured on the new host · old server retired
A practical migration checklist, grouped by phase.

And the rollback plan, which you hopefully never use: because the old server is still running and untouched, rolling back means pointing DNS back to the old IP. With a short TTL, that takes minutes. This is exactly why you lower the TTL first and why you don’t cancel the old hosting on migration day.

A migration is also a good moment to improve things: update PHP, remove plugins you no longer use, and check performance on the new host. My Core Web Vitals checklist and security hardening checklist are good companions for the week after launch.

Key takeaways

  • Back up everything first and keep the old site running until the new one is proven.
  • Never use a raw SQL find-and-replace on WordPress data. Use wp search-replace, with a dry run first.
  • Test with a hosts-file preview before changing DNS.
  • Lower the DNS TTL a day or two ahead so the switch (and any rollback) takes minutes.
  • For stores, freeze orders during the final database sync and place a real test order afterwards.

Planning a move to a new host, a new domain or HTTPS, and want it done without surprises? I handle WordPress and WooCommerce migrations end to end, including the testing. Tell me about your site and where it needs to go.

Code examples in this post are free to use under the MIT licence. Article text © 2026 Farooq Bin Munir.