The migration looked clean. Files copied, database imported, DNS pointed at SkyServer, site loads. Then you start clicking around and something's off: the logo is a broken image icon, a few internal links redirect back to the old host, and a widget you know you configured is just blank. None of that is a coincidence, and none of it means the migration failed — it means WordPress stored some of your URLs in a format a plain find-and-replace can't touch safely.

Symptom: The Site "Mostly" Works, But Pieces Are Wrong

This almost never looks like a total failure. Posts and pages usually render fine, because their content is stored as plain text in wp_posts. What breaks is scattered and inconsistent:

  • The site logo, favicon, or a theme background image shows a broken icon
  • Some links go to olddomain.com instead of the new one, even though you changed siteurl and home
  • A text widget, menu item, or Customizer setting (theme mods) just disappeared
  • Page builder layouts (Elementor, Divi, WPBakery) look stripped down or half-rendered
  • Plugin settings reset to defaults even though you didn't touch them

If you or whoever handled the migration ran an SQL query like UPDATE wp_options SET option_value = REPLACE(option_value, 'olddomain.com', 'newdomain.com') in phpMyAdmin, that's almost certainly the cause.

Cause: WordPress Serializes Data, and Replace Breaks the Length Count

A lot of WordPress data isn't stored as plain text — it's stored as serialized PHP. Theme settings, widget content, some plugin options, and more all get saved as a single string that looks like this:

a:2:{s:6:"header";s:29:"https://olddomain.com/logo.png";s:4:"text";s:12:"Welcome here";}

Look closely at s:29: right before the URL. That number is the exact character length of the string that follows. PHP uses it to know where that value ends and the next one begins. A plain text find-and-replace swaps olddomain.com for newdomain.com just fine — but if the new domain is a different length, that 29 is now wrong. PHP tries to unserialize the array, hits a length that doesn't match the actual string, and gives up. Depending on the WordPress version and where the data lives, that field silently returns empty, throws a warning, or drops the whole array.

This is why the damage is so uneven. Plain wp_posts content survives because it's just text. Anything serialized — widgets, theme mods, some page builder data, several popular plugins' settings — gets corrupted the moment the string length changes.

Fix: Use a Serialization-Aware Search & Replace

The tool has to understand PHP serialization, recalculate those length prefixes, and rebuild the array correctly. Don't run another raw REPLACE() query — it'll make the corruption worse, not fix it. Here are three ways to do it properly, in order of preference.

Option 1: WP-CLI (fastest, if you have SSH access)

If your SkyServer plan includes SSH, this is the cleanest route. First run it as a dry run so you can see exactly what would change before committing:

wp search-replace 'https://olddomain.com' 'https://newdomain.com' --all-tables --precise --dry-run

Review the output — it lists every table and the number of replacements it would make. If that looks right, drop --dry-run and run it for real:

wp search-replace 'https://olddomain.com' 'https://newdomain.com' --all-tables --precise

--all-tables catches custom tables some plugins create outside the standard WordPress set (WooCommerce, form plugins, page builders often do this). --precise avoids some edge-case matching bugs with multibyte strings.

Option 2: A Migration Plugin (no SSH needed)

If you're on shared hosting without terminal access, install Better Search Replace (or a similar tool) from the WordPress plugin repo through your dashboard. Go to Tools > Better Search Replace, enter the old and new domain, select all tables, and tick Run as dry run first. Check the preview, then run it for real. It handles the serialization math internally, so widgets and theme settings come back intact.

Option 3: A Standalone Script via phpMyAdmin

If the site won't load at all (so plugins aren't an option) and you don't have SSH, you can temporarily upload the Interconnect/IT Search-Replace-DB script to a folder in public_html, run it through the browser, and point it at the same database credentials from wp-config.php. This works entirely outside WordPress, which is useful when the site is broken enough that the admin dashboard won't even load.

Delete the script file immediately after you're done. It has full read/write access to your database with no login of its own — leaving it sitting in public_html is a real security hole, and bots do scan for it by name.

Comparing the Three Methods

MethodNeeds SSHWorks if wp-admin is brokenBest for
WP-CLIYesYesVPS or SSH-enabled cPanel accounts
Better Search Replace pluginNoNo — needs dashboard accessShared hosting, site otherwise loads fine
Search-Replace-DB scriptNoYesSite is down and you need a quick fix, then delete it

Prevention: Get It Right on the Next Migration

  • Never run a raw SQL REPLACE() on wp_options, wp_postmeta, or any table you know holds serialized data.
  • Always do the URL swap with WP-CLI or a proper search-replace plugin, and always dry-run it first.
  • If you're cloning through Softaculous's built-in clone/staging tool, it already handles this correctly — you don't need to run search-replace again afterward.
  • After the swap, clear any object cache and page cache (Redis, LiteSpeed Cache, or a caching plugin) — stale cached HTML can still show old URLs even after the database is fixed.
  • Check wp-config.php too. If it hardcodes WP_HOME or WP_SITEURL constants, a database search-replace won't touch those — you'll need to edit the file directly.

If you migrated to SkyServer and you're seeing this, open a support ticket with your domain and we can run a serialization-safe search-replace on the account directly — it's usually a five-minute fix once we're in.

Frequently Asked Questions

Will WP-CLI's search-replace break anything else on my site?

Not if you run --dry-run first and review the table of changes. It only touches exact string matches, so unrelated content is untouched. The main risk is running it without reviewing the dry run and catching an unintended match, like a domain name that also appears as plain text somewhere unrelated.

Why did Softaculous's clone feature not have this problem?

Softaculous's built-in clone tool already runs a serialization-aware search-replace as part of the cloning process. The corruption described here happens when someone bypasses that and edits the database directly with a generic SQL tool instead.

My widgets are gone completely, not just showing wrong URLs. Is this the same issue?

Yes, usually. Widget data is stored as one large serialized array in wp_options under widget_text, widget_custom_html, and similar keys. If the length prefix for any single widget's content gets thrown off, PHP can fail to parse the entire array, which makes every widget in that sidebar disappear rather than just the one with the broken URL.

Do I need to fix this in the database, or can I just edit pages in the WordPress editor?

Fix it in the database. Editing pages one by one only fixes what you can see and doesn't touch theme mods, widget data, or plugin settings sitting in wp_options. A proper search-replace run fixes the whole site in one pass instead of chasing broken links page by page.

Is it safe to run search-replace on a live production site?

Take a database backup first — export it from phpMyAdmin or run wp db export before you start. The operation itself is quick, but if the dry run's replacement count looks way higher or lower than expected, you want a backup to fall back to rather than trying to reverse a bad run by hand.