WordPressMigrationMaintenanceTroubleshootingWordPress 6.xWordPress 7.xIntermediateCurrent

How to Move WordPress to a New Domain Without Breaking Serialized Data

A field-tested WP-CLI workflow for changing a WordPress site URL: exports, wp option update, search-replace with dry runs, GUID protection and post-migration cleanup.

Why domain changes break WordPress installs

A WordPress site carries its own URL in far more places than the two rows you can see in Settings > General. The home and siteurl options are the visible tip. The rest of the database is full of the old address: hard-coded links inside post content, URLs in widget and theme options, attachment references in post meta, and — the part that actually breaks things — PHP-serialized arrays where the length of each string is stored next to the string itself.

Run a plain SQL UPDATE ... REPLACE over those tables and every serialized string whose length changes gets a length prefix that no longer matches its content. PHP refuses to unserialize it, and widgets, page-builder settings and plugin configuration silently revert to defaults. WordPress’s own migration documentation is blunt about this: a blanket search-and-replace on the database is unsafe because some themes and widgets store values “with the length of your URL marked”, and it points admins toward tools that understand serialized data — including WP-CLI’s search-replace — instead of raw SQL.

This guide walks through the full sequence we use when moving a client site to a new domain or promoting a staging build to production: back up, flip the two core options, run a serialized-data-aware replacement with a dry run first, leave GUIDs alone, then clean up the leftovers. It assumes WP-CLI is installed and that you are comfortable with the basics covered in our WP-CLI update workflow.

Step 0: Export the database before touching anything

Every step after this point modifies the database, and direct database changes are permanent. Start with an export you can actually restore from:

wp db export backup-$(date +%Y%m%d-%H%M).sql

wp db export runs mysqldump using the credentials in wp-config.php and writes a plain SQL file, so the backup is portable across hosts — you are not locked into a proprietary snapshot format. If the database is large and you only want the content tables, --tables=<tables> and --exclude_tables=<tables> let you scope the dump.

If the file copy of the site is also moving between servers, take a file backup at the same time (wp-content at minimum, ideally the whole install). And if the new server has different database credentials, this is the moment to update DB_NAME, DB_USER, DB_PASSWORD and DB_HOST in wp-config.php — the connection problems that follow a missed credential change look exactly like the failure mode in our guide to fixing the WordPress database connection error, and it is easier to rule that out before the URL work starts.

Step 1: Decide the canonical URL and set the two core options

Before replacing anything, decide the final form of the address: https:// or http://, www. or bare apex. Every later replacement only works cleanly if the search and replacement strings are exact, so pick once and stay consistent.

The two options that define the site address can be updated directly with WP-CLI:

wp option update siteurl 'https://newdomain.com'
wp option update home 'https://newdomain.com'

wp option update writes the option through WordPress’s own option API, which means the values are serialized correctly on the way in — no manual database editing required. The WordPress migration guide also documents define( 'WP_HOME', ... ) and define( 'WP_SITEURL', ... ) constants in wp-config.php as a way to hard-code the URL, but note the trade-off: with the constants in place, the fields in Settings > General become read-only. The constants are useful as a temporary rescue when a site is unreachable; for a permanent move, the database options are the cleaner end state.

One warning from the official documentation worth repeating: the RELOCATE constant should never be left enabled. It exists so you can log in at the new URL and fix settings from the admin screen, but left in place it “allows an attacker to change your site URL to anything they want in some configurations”. If you use it at all, remove the line as soon as the move is done.

Step 2: Run the replacement with a dry run first

With the core options pointing at the new domain, replace the old URL everywhere else. The command that does this safely is wp search-replace, which understands PHP serialization: by default it uses fast SQL queries and automatically switches to PHP processing for columns that contain serialized data, so string lengths are rewritten along with the strings.

Always preview before committing:

wp search-replace 'https://olddomain.com' 'https://newdomain.com' \
  --dry-run --report-changed-only

--dry-run executes the entire operation and prints the report but saves nothing to the database. --report-changed-only trims the output to the fields that would actually change, which matters on a site with thousands of rows. Read the report: the table-by-table count tells you whether the scope matches your expectations before anything is written.

If the report looks right, commit:

wp search-replace 'https://olddomain.com' 'https://newdomain.com' \
  --skip-columns=guid --report-changed-only

Two flags deserve explanation:

--skip-columns=guid. The GUID column in wp_posts “must remain unchanged”, in the words of the migration guide. GUIDs are permanent identifiers for feed readers; rewriting them makes every previously published post appear as new content in subscribers’ feeds. Excluding the column is not optional hygiene — it is the documented requirement.

--precise, when you need it. If your content contains deeply nested serialized structures — common with page builders and complex plugin settings — --precise forces PHP to process every column instead of falling back to SQL where possible. It is slower, but it “handles complex serialized data structures more reliably”. A sensible order: run the default first, spot-check a page-builder page and your widgets, and reach for --precise if anything came out wrong after the restore-and-retry.

Step 3: Match the scope to your database

By default, wp search-replace touches the tables registered to the $wpdb object — in other words, the standard WordPress tables for the current site. That covers posts, options, post meta and user meta, which is where almost all URLs live.

Two situations call for a wider scope. If plugins created their own tables with the same prefix and store URLs in them, --all-tables-with-prefix extends the replacement to every table matching your prefix. If the database contains multiple installs sharing a prefix, or tables you want to sweep regardless of prefix, --all-tables covers everything — note that it overrides the prefix and network scoping, so only use it when you know what else is in that database. On multisite, the default is the current site’s tables; --network widens it to all sites.

A word of caution from the same command reference: --regex mode exists but is dramatically slower — the documentation estimates roughly 15–20x — and is rarely necessary for a plain domain swap. Reserve it for cases like changing protocol-relative or mixed-protocol URLs where a regular expression genuinely earns its cost.

Step 4: Clean up the leftovers

Even after a clean replacement, a few places keep references to the old domain. The ones worth checking:

Transients. Cached values under wp_options can hold stale URLs. They regenerate on their own, so deleting them is safe — either via SQL as the migration guide shows, or simply with wp transient delete --expired followed by a cache flush on your object cache and page cache layers.

Media and attachment URLs. Old-protocol or old-domain references in post_content should have been caught by the replacement, but check individual posts that embed external media manually.

Hard-coded values in themes and plugins. Some code stores URLs outside the database — in theme options files, custom plugins or widgets that reference uploads by absolute path. Search the codebase: wp package aside, a plain grep -r "olddomain.com" wp-content/ on the server finds what the database replacement cannot touch.

Permalinks and .htaccess. If the server configuration changed with the move, regenerate rewrite rules by visiting Settings > Permalinks and saving, or run wp rewrite flush. Subdirectory-to-root moves in particular need their rewrite rules rebuilt.

Search engines. If the old domain stays under your control, keep 301 redirects from old URLs to new ones rather than letting them die. Redirects preserve accumulated ranking signals while crawlers re-index the new address — part of the wider platform decisions covered in our migration strategy article.

Step 5: Verify before you call it done

Work through a short checklist on the new domain: log in through wp-login.php, load the homepage and a deep post, confirm images resolve, check that widgets kept their configuration rather than resetting, and view a feed to confirm post dates look normal (a symptom of rewritten GUIDs). Then flush every cache layer once more.

For teams that maintain WordPress sites professionally — whether built in-house or through a partner like our WordPress development service — the value of this workflow is repeatability. The same commands move a staging build to production, a client site to a new domain, or an HTTP site to HTTPS with only the search and replacement strings changing. Compare that with click-path migration through the admin screens, and the case for scripting the move at the platform-selection stage rather than as an afterthought is easy to make.

Related guides

Continue with the surrounding work.

More WordPress guides
Related insights

Broader context from the YCTheme blog.

Article

When to Migrate a Website to a New Platform

A decision framework for moving between WordPress, Joomla, TYPO3 and Shopify: the signals that justify a migration and how to plan one without losing search visibility.