Sometimes you need to change text everywhere in a WordPress site: an old domain after a migration, http:// to https://, a changed upload path, a renamed company, or an outdated email address. Doing this in the database is fast, but there’s one trap that breaks sites: serialized data.
Why raw SQL REPLACE is dangerous
WordPress and plugins store arrays and objects as serialized PHP strings, which include each string’s length:
a:1:{s:3:"url";s:22:"http://old-example.com";}
If you replace http://old-example.com (22 characters) with https://new-example.com (23 characters) using SQL REPLACE(), the stored length s:22 no longer matches. PHP then can’t unserialize the value, and widgets, theme options or plugin settings silently reset or disappear.
Tools that understand serialization fix the lengths for you. Use them.
Method 1: WP-CLI (recommended)
cd /var/www/example.com
# preview what would change
wp search-replace 'http://old-example.com' 'https://new-example.com' --all-tables --dry-run --report-changed-only
# do it
wp search-replace 'http://old-example.com' 'https://new-example.com' --all-tables --precise
# then clear caches
wp cache flush
Useful options:
| Option | Purpose |
|---|---|
--dry-run |
Show counts without changing anything |
--all-tables |
Include non-prefixed and plugin tables (default: only WordPress-registered tables) |
--precise |
Use PHP for every row: slower, but handles tricky serialized data |
--skip-columns=guid |
Leave post GUIDs alone (the default for domain changes is debated; GUIDs shouldn’t normally change on a live site) |
--include-columns=post_content |
Limit to specific columns |
--regex |
Use a regular expression (careful) |
--export=file.sql |
Write a modified SQL dump instead of changing the live database |
--network |
All sites in a multisite network |
Always back up first: wp db export before-replace.sql. See WP-CLI guide.
Method 2: plugins
If you don’t have shell access:
- Better Search Replace: select tables, dry run, serialization-safe.
- Search & Replace and similar tools work the same way.
On large databases, plugins can time out, because they run inside PHP requests. WP-CLI has no such limit.
Method 3: during migration
Migration tools (Duplicator, WPvivid, All-in-One WP Migration) run a serialization-safe replace automatically when you restore to a new URL. Check their logs.
Common replacements
| Task | Search → Replace |
|---|---|
| Domain move | https://old.com → https://new.com |
| Force HTTPS in content | http://example.com → https://example.com |
| Staging to live | https://staging.example.com → https://example.com |
| CDN removal | https://cdn.example.com/wp-content → https://example.com/wp-content |
| Email change | [email protected] → [email protected] |
Tips:
- Don’t include a trailing slash in the search term unless you mean it.
- Run separate passes for
http://,https://and protocol-relative//old.comvariants if the content mixes them. - Page builders (Elementor and others) may cache CSS with old URLs. Regenerate their CSS and files afterwards (Elementor → Tools → Regenerate files and data).
When raw SQL is OK
For columns that never hold serialized data, such as post_content in wp_posts, raw SQL is fine:
UPDATE wp_posts SET post_content = REPLACE(post_content, 'http://old.com', 'https://new.com');
But wp_options, wp_postmeta, wp_usermeta and many plugin tables do contain serialized values. Use WP-CLI for those.
After the replace
- Flush all caches: object cache, page cache plugin, Nginx FastCGI cache, CDN.
- Re-save permalinks (Settings → Permalinks → Save).
- Spot-check pages, menus, widgets and theme settings.
- Scan for mixed content in the browser console (on HTTPS sites).
Quick reference
| Task | Command |
|---|---|
| Backup | wp db export backup.sql |
| Preview | wp search-replace old new --all-tables --dry-run |
| Replace | wp search-replace old new --all-tables --precise |
| Multisite | add --network |
| Flush caches | wp cache flush |
Related: change the WordPress site URL and WordPress staging site.