← Back to blog

How to migrate a WordPress site to a new host without a broken hour

The SandyWP team 9 min read

Migrating a WordPress site to a new host is three steps in a fixed order: copy the files and database to the new server, verify the copy works there, then point DNS at it.

Everything that goes wrong in a migration comes from doing those out of order, or from skipping the middle one because the site "looked fine" in a browser.

This post is about the middle step, because that is the part the ranking guides treat as a formality.

We read the guides currently ranking for this phrase on 2026-10-23. Most of them recommend editing your hosts file to preview the new server, and not one of them says what that preview cannot test.

The short version

  1. Lower the TTL on your DNS records to 300 seconds, a day before you plan to move.
  2. Copy wp-content, wp-config.php and a database dump to the new host.
  3. Create the database and user on the new host, and point wp-config.php at them.
  4. Fix any URL that changed, with wp search-replace, not with SQL find-and-replace.
  5. Verify the site on the new server, including the things a browser never touches.
  6. Freeze writes on the old site, take a final database dump, import it, then change DNS.
  7. Re-save permalinks, re-issue SSL, and re-test email and cron on the new host.

If the site takes orders, bookings or form submissions, step 6 is the one that costs money when you get it wrong. More on that below.

Rehearse the move before you make it

The cheapest way to find out what breaks on the new stack is to break it somewhere that does not matter.

Clone production into a throwaway copy, run the whole migration routine against the copy, and write down every error you hit. Then do the real move already knowing which plugin fatals on PHP 8.3 and which cron job silently stopped.

This is what SandyWP is for. The Cloner plugin reads your database and files, never writes to them, and lands a disposable sandbox with clean WordPress core and your content restored.

Your live site is untouched while you practise.

A sandbox is also where you check the new host's PHP version against your plugins before you commit to the plan. Upgrade testing and host migration are the same job: run the thing you are afraid of, somewhere it cannot hurt you.

The honest limit: a sandbox is not your new host. It will not tell you whether that host's object cache, its PHP memory limit, or its outbound mail is configured the way you need.

It tells you whether your code and content survive a fresh stack, which is most of what actually breaks.

Copy the files and the database

Nothing clever here. Over SSH, if the new host gives you a shell:

# on the old host
wp db export dump.sql --add-drop-table
tar -czf wp-content.tar.gz wp-content

Then create the database and user on the new host, upload both archives, and import:

# on the new host
tar -xzf wp-content.tar.gz
wp db create
wp db import dump.sql

Copy wp-config.php across as a reference, but edit the four database constants to match the new server rather than hoping the old credentials exist there.

Do not copy wp-content/cache, wp-content/upgrade, or any backup-plugin directory. They are large, they are regenerated, and a stale page cache is a very confusing way to start on a new server.

If the new host offers a migration service, take it. The point of doing it by hand is knowing what to check afterwards, not proving you can.

Two mismatches that break the import

Imports fail far more often from a server difference than from a bad dump.

Collation. A dump taken from MySQL 8 can carry utf8mb4_0900_ai_ci or utf8mb4_unicode_520_ci, and importing it into MariaDB produces Unknown collation. The fix is a sed over the dump before the import, and we wrote it up in the unknown collation error.

PHP version. The new host is almost certainly on a newer PHP than the old one, and that is the point of moving, but it is also where an abandoned plugin dies. Check the versions on both sides before you start, then test on the new version with debug logging on.

Both of these are much nicer to discover on a copy on a Tuesday than during a cutover on a Friday.

Fix the URLs without corrupting the database

If the domain is not changing, you have no URLs to fix. Skip this section.

If it is changing, the official advanced administration handbook is blunt about the risk: a search and replace over the whole database "can cause issues with data serialization", because serialized values store the byte length of each string (developer.wordpress.org).

Change a URL inside a serialized array with SQL and the length prefix no longer matches, so PHP discards the value. Widgets empty themselves, theme options revert, and nothing in the logs says why.

Use WP-CLI, which the docs say "intelligently handles PHP serialized data":

wp search-replace 'https://old.example' 'https://new.example' --dry-run
wp search-replace 'https://old.example' 'https://new.example' --precise --skip-columns=guid

Three flags earn their keep. --dry-run reports every change without writing any, --precise forces PHP replacement on every column instead of SQL where it can get away with it, and --skip-columns=guid leaves post GUIDs alone, since feed readers key off them.

By default the command only touches tables registered to $wpdb. If the site has tables from a plugin that live outside that set, add --all-tables.

There is more on this in our WP-CLI search-replace post and on checking serialized data afterwards.

What the hosts-file preview actually tests

Here is the gap in the popular guides. They tell you to add a line to /etc/hosts pointing your domain at the new server's IP, load the site, and if it looks right, switch DNS.

That preview is real and worth doing. But it only redirects name resolution on your own machine, so what you are testing is: can my browser render this site from that IP.

Everything that arrives from outside your laptop still goes to the old server, because the rest of the internet is still resolving the old address. That includes:

  • payment gateway and webhook callbacks, which keep hitting the old site
  • WP-Cron, if a real system cron on the old host is pinging it
  • transactional email, where the new server's IP and SPF alignment are what matter
  • Let's Encrypt HTTP-01 validation, which resolves the public record, not your hosts file
  • uptime monitors, OAuth redirect URIs, and any third party with your domain stored

So verify those deliberately instead of inferring them from a working homepage. Hit the new server directly with curl --resolve so you are testing the server rather than your browser cache:

curl -sI --resolve 'example.com:443:203.0.113.10' https://example.com/
curl -s --resolve 'example.com:443:203.0.113.10' https://example.com/wp-json/ | head -c 200

Then log in on the new server, send a real test email, run wp cron event list, and check that the admin shows no fatal errors with WP_DEBUG_LOG on. Debug mode is the fastest way to read that log when you are doing this on a sandbox copy rather than over SSH.

DNS: TTL is the part you control

Guides describe DNS propagation as a fixed 24 to 48 hours you simply endure. Most of it is a number you set.

A resolver caches your record for as long as its TTL says, and Cloudflare's own documentation puts it plainly: a longer TTL "means that updates to your records take longer to go into effect" (developers.cloudflare.com). Their Auto value is 300 seconds.

So lower the TTL on the A and AAAA records to 300 a day before the move. Resolvers pick up the low value while you are still pointing at the old host, and when you flip the address the window is five minutes rather than a day.

Two caveats worth knowing. Changing nameservers rather than records is governed by the TTL of the delegation in the parent zone, which you do not control and which is often measured in days: prefer changing A records at your existing DNS provider.

Some ISP resolvers and OS caches also ignore short TTLs, which is why the old server has to keep serving for a while after the switch.

Keep the old host running for at least a week. It costs one more month of a cheap plan and it is the only thing standing between a stale resolver and a reader seeing nothing.

The window where writes land on two servers

This is the part nobody mentions, and it is the one with a real cost.

During the TTL window, some visitors resolve to the old server and some to the new one. Both are running WordPress, both have a writable database, and the two databases have stopped being the same thing.

An order placed on the old server after you took your final dump exists only there. A comment, a form entry, a new user, a stock decrement: any write that lands on the losing server is gone the moment you stop pointing at it.

For a brochure site this does not matter. For anything transactional, close the window instead of hoping:

  1. Put the old site into maintenance mode, so it stops accepting writes.
  2. Take the final database dump and import it on the new host.
  3. Change the DNS record.
  4. Leave maintenance mode on the old server until its TTL has fully expired.

That gives you a few minutes of a maintenance page instead of a day of split writes. Maintenance mode is a one-file trick, and it is cheaper than reconciling two databases by hand.

If you cannot take the site down at all, the migration is a replication problem rather than a copy problem, and that is a job for your new host's engineers.

After the cutover

Six checks, in the order they usually fail:

  1. Permalinks. Re-save Settings, Permalinks to regenerate the rewrite rules on the new server. More on permalinks.
  2. SSL. Confirm the certificate on the new host covers both the apex and www, and that nothing is still loading over HTTP.
  3. Email. The sending IP changed, so test a real message and check SPF, DKIM and DMARC against the new host. Email not sending covers the usual causes.
  4. Cron. wp cron event list should show due events running. If the old host had a system cron hitting wp-cron.php, recreate it.
  5. Search engine visibility. Make sure the new install is not discouraging indexing, and that no robots.txt came across from a staging copy. More on robots.txt.
  6. File ownership. If you unpacked the archive as root, PHP cannot write to uploads. Fix the ownership before you find out through a failed upload.

Then watch the logs for a day rather than declaring victory. A migration that looks perfect in the browser can still be failing one scheduled job an hour.

When a copy is the wrong tool

Be honest about what a disposable copy does not cover.

It cannot test the new host's network, its mail reputation, its object cache or its real resource limits, because it is not that host. It cannot rehearse DNS either.

And it tells you nothing about load, because nobody is hitting it.

What it does is remove every content-and-code surprise from the cutover, which is where most migration pain actually lives. Use a clone to learn what breaks, then do the real move on a quiet morning with the old host still paid up.