Hosting

How to Migrate WordPress Without Downtime, and the Two Steps That Break It

Affiliate disclosure: Some links on this page are affiliate links. If you sign up or make a purchase through them, we may earn a commission at no extra cost to you. This never affects what we recommend or how we rank options.

Migrating a WordPress site is not difficult. It is a sequence of about eight steps with two genuine traps, and the official documentation covers the sequence thoroughly while barely covering the traps.

The traps are: doing the URL replacement with a naive SQL statement, and cutting DNS over without accounting for caches you do not control. Both are recoverable. Both cost hours if you meet them unprepared.

The documented sequence

WordPress.org’s own migration guide is more conservative than most tutorials, and worth following as written. It applies to a single installation — multisite is explicitly out of scope, which is a limitation worth noticing before you plan a day around it.

The published order:

  1. Back up the database and the files
  2. Change the URLs in Settings → General on the source site
  3. Re-download the files so the export is consistent
  4. Re-export the database
  5. Edit wp-config.php to match the destination
  6. Upload the files to the destination
  7. Import the database at the destination
  8. Verify

Two things about that order are load-bearing. Step 2 happens on the source site, before the export — because the URL values live in the database, and changing them in the admin interface is the safe way to do it. And step 3 exists because files can change between the database dump and the file copy; re-downloading closes the window.

If your site is small and nobody is editing it, that window is trivial. If it is an active commerce site, the window is where you lose an order.

Trap 1: replacing URLs with SQL

This is the one that produces silent, strange corruption.

URLs in a WordPress database are not stored as isolated strings. Many are inside serialised PHP data structures — plugin settings, widget configurations, theme options — where the string length is encoded alongside the value. A plain SQL REPLACE changes the text but not the length prefix, and the result is a value that PHP cannot unserialise. The symptom is not an error message. It is a settings page that renders blank, or a theme option that quietly reverts.

WordPress.org’s own guidance is explicit: “If you do a search and replace on your entire database to change the URLs, you can cause issues with data serialization.” Note the framing — as a hazard, not as an instruction with a safe alternative attached.

The safe tool is WP-CLI, and its documented behaviour is exactly the property you need: search-replace “intelligently handles PHP serialized data, and does not change primary key values.”

# Always look first
wp search-replace 'https://old.example' 'https://new.example' --dry-run --all-tables

# Then do it
wp search-replace 'https://old.example' 'https://new.example' --all-tables

Three flags worth knowing:

  • --dry-run reports what would change without changing it. There is no reason to skip this.
  • --precise forces PHP-based replacement instead of SQL. The documentation describes it as “more thorough, but slower” — slower is the correct trade here.
  • --skip-columns=guid is documented as an option, and it exists because of the next point.

The column you must never touch

Both WordPress.org guides are unusually emphatic about one thing, and the emphasis is telling:

“Never, ever, change the contents of the GUID column, under any circumstances.”

GUIDs look like URLs, which is why people include them in a search-replace. They are not URLs. They are permanent identifiers, and WordPress uses them in feed generation. Changing them causes duplicate feed entries and unpredictable behaviour in subscribers’ readers — and because nothing visibly breaks on your own site, you will not discover it for weeks.

The documented consequence of leaving them alone is benign: “references to the old domain name or location will remain in the database, and that can cause issues with links or theme display.” That is the trade the documentation is recommending — leave the identifiers stale, keep the system correct.

Testing before you cut over

There is a documented way to verify the new installation before DNS moves, and it is underused.

WP_HOME and WP_SITEURL in wp-config.php override the database values for home and siteurl. They do not write to the database — remove the lines and the site reverts to whatever is stored. That is precisely the property you want for testing: point WP_HOME at the staging URL to inspect the site, and let the stored values be the ones that go live.

Because they do not persist, they are not a migration mechanism. They are a temporary override, and something like the RELOCATE constant is the documented way to actually change the stored value.

Working around the read lock

If you have ever had mysqldump fail with a permission error about LOCK TABLES, the cause is documented. mysqldump runs --opt by default, which expands to include --lock-tables and --quick. On a shared host that withholds the LOCK TABLES privilege, that combination fails.

--single-transaction is the documented alternative: it issues a BEGIN before dumping, removing the LOCK TABLES requirement. For InnoDB tables it gives you a consistent snapshot without locking anything — which also means the site stays up during the dump.

Trap 2: the DNS cutover

Here is where the official documentation stops. WordPress.org gives a change-order checklist. There is no official zero-downtime cutover playbook, because the part that decides downtime is not WordPress at all — it is DNS and its caches.

The mechanics that matter:

TTL is the control you have. The record’s time-to-live determines how long resolvers keep your old value after you change it. Lower it to something short well before the migration — days before, not minutes — so that when you flip, the old value expires quickly everywhere.

Negative answers are cached too. This is the part most guides omit. When a resolver receives NXDOMAIN or NODATA, that negative answer is cached, with a TTL derived from the zone’s SOA record — and the caching window is typically recommended in the range of hours. So a record that briefly failed during your migration keeps failing for resolvers that asked at the wrong moment, even after you have fixed it. Plan for the possibility of a longer tail than the TTL suggests.

TTL counts down. A resolver holding your answer does not restart the clock when you change the record upstream. It serves the remainder of the original TTL. This is why you lower the TTL before you need it to be low.

Verify against resolvers, not your laptop. Query public resolvers explicitly, and then test the actual site:

dig @1.1.1.1 new.example.com A +short
dig @8.8.8.8 new.example.com A +short
curl -sI https://new.example.com/ | head -5

Test the oldest thing people use. If your email is on the same domain, send a message in and out before you consider the migration complete. See the DNS and email article for the ordering rules that make that safe.

Host-supplied migrators, and the failure they do not advertise

Managed hosts increasingly offer to do the migration for you, and the shape is almost always a plugin plus a transfer token. SiteGround’s version is worth walking through because its documented limits are specific, and because the limits tell you where the manual sequence above is still the only option.

The published order of operations:

  1. In the destination control panel, generate a migration token (Site Tools → WordPress → Migrator).
  2. Install the host’s migrator plugin on the source site. SiteGround’s is SiteGround Migrator, free, from the official plugin repository.
  3. Paste the token and initiate the transfer. It runs server-side, so closing your browser does not stop it.
  4. The host issues a temporary URL, live for 48 hours, so you can inspect the copy before DNS moves.

The documented exclusions are the part to read before you plan a day around it: no localhost installs, no migration from WordPress.com, no full Multisite network, and WordPress content only — anything on the account outside the WordPress install is left behind.

Now the part the marketing does not mention. This class of tool has a specific and demoralising failure mode: the transfer reports success, and what arrives is a default WordPress install with none of your content. At the time of writing the plugin’s own review page carries recent one-star reports of exactly that, alongside a report from a user whose transfer stalled and who resolved it by disabling caching and non-essential plugins on the source site before retrying.

So the rule for this whole category is: treat “migration complete” as a claim to verify, not a result. Open the temporary URL, count the posts, load a page that uses a plugin shortcode, then move DNS. The 48-hour window is the feature that makes a failure survivable — a failure you cannot inspect is the one that costs a weekend. The host-agnostic version of the switch is the one to read when the destination host supplies no migrator at all.

If migration tooling is genuinely part of how you are choosing a host, the free first-party migrator is a real advantage, and it is limited to hosts that provide one. SiteGround is one of them. Two caveats before you take that as a recommendation: the tool moves sites in and cannot move them out, so it is not a reason you can count on later in the other direction; and this is not a budget host, so check the renewal rate as well as the first-term price.

When to not do this yourself

If the site takes orders or handles logins, the cost of a botched migration is measured in customers, not hours. The sequence above is not complex, but step 1 — a verified backup you have actually restored once — is the step that separates a recoverable migration from an unrecoverable one.

Practice the restore. A backup you have never restored is a hypothesis.

Sources

Primary documentation only — the provider's own page, the specification, or the standard itself. Claims that rest on anything else are described in the text as a market pattern or an inference, not as documentation.

  1. WordPress — Moving WordPress (the serialised-data warning and the GUID rule, both quoted in this article)
  2. WP-CLI — wp search-replace (the replacement that understands PHP serialised data and leaves primary keys alone)

Written by . Spotted something out of date? Tell us.