Je hebt WordPress verhuisd naar een nieuwe host of het domein gewijzigd, en nu geven de helft van de afbeeldingen een 404, rendert de pagebuilder lege vakken, wijzen menu-items nog naar het oude domein en geeft het contactformulier een foutmelding. De oorzaak is bijna altijd hetzelfde: in de database staat de oude URL nog hardcoded op tientallen plekken, en de tool die hem moest bijwerken deed de verkeerde soort replacement, sloeg tabellen over, of sloopte serialised data. Dit artikel is de dedicated referentie voor alleen die search-replace-stap. Het behandelt drie tools in volgorde van voorkeur, de Gutenberg JSON-escape-val die in vrijwel elke tutorial ontbreekt, en de dry-run en verificatieworkflow waarmee je problemen ziet voordat ze productie raken.
De volledige verhuis-walkthrough eromheen (bestanden kopieren, DNS omzetten, testen op de nieuwe host) staat in een apart artikel. Heb je het hele plaatje nodig? Begin dan bij WordPress verhuizen naar een nieuwe host of domein en kom hier terug voor de search-replace-details.
Wat je wilt bereiken
Een URL search-replace draaien op een WordPress-database, zodat iedere hardcoded verwijzing naar de oude URL, inclusief URLs binnen serialised PHP en URLs die escaped in Gutenberg-block-JSON zitten, de nieuwe URL wordt, zonder data te beschadigen.
Wat je van tevoren nodig hebt
- Een verse backup van de database. Niet de geplande backup van gisteren. Een dump die je vlak voor je begint maakt, opgeslagen buiten de server waar je zo aan gaat zitten. Een search-replace herschrijft in een klap rijen in meerdere tabellen, en een verkeerde run is sneller terug te zetten uit een backup dan handmatig te corrigeren.
- De exacte oude URL en de exacte nieuwe URL, inclusief protocol (
https://ofhttp://) en zonder trailing slash.https://example.devenhttps://www.example.devzijn twee verschillende strings en vragen om twee aparte runs. - Schrijftoegang tot de database. Via WP-CLI (die leest
wp-config.php), via WordPress-adminrechten voor de plugin-methode, of via directe database credentials voor de standalone-script-methode. - Een staging-omgeving of pre-launch-omgeving waar je de replace kunt draaien en verifieren voordat je productie raakt. Search-replace op een live site is het laatste redmiddel, niet de default.
Waarom een platte SQL REPLACE() WordPress kapot maakt
Voor je naar de stappen gaat, dit is het ding dat elke WordPress-developer moet internaliseren: je kunt niet gewoon UPDATE wp_posts SET post_content = REPLACE(post_content, 'old', 'new') op een WordPress-database draaien. De officiele WordPress-verhuisdocumentatie is er glashelder over: "If you do a search and replace on your entire database to change the URLs, you can cause issues with data serialization."
De reden is dat WordPress een hoop van zijn configuratie opslaat als serialised PHP, de tekstoutput van PHP's serialize() functie. Widget-instellingen, theme-mods, menu-structuur, pagebuilder-layouts (Elementor, Beaver Builder, Divi) en de meeste plugin-opties zijn serialised PHP-blobs in wp_options, wp_postmeta, wp_termmeta en vergelijkbare tabellen. Een serialised array ziet er zo uit:
a:2:{s:4:"home";s:22:"https://old-domain.com";s:7:"siteurl";s:22:"https://old-domain.com";}
De prefix s:22 betekent: "de volgende string is 22 bytes lang". Draai je nu REPLACE('https://old-domain.com', 'https://new-domain.com') via SQL, dan verandert de string-waarde maar toevallig is de nieuwe waarde ook 22 bytes. Mazzel. Maar https://staging.example.dev naar https://example.com verandert het aantal bytes wel. De s:N-prefix loopt niet mee. PHP's unserialize() leest de prefix, probeert N bytes te lezen, krijgt iets anders terug dan verwacht, en geeft false. De widget verdwijnt. Het menu is leeg. De pagebuilder rendert een lege div. De plugin valt terug op zijn defaults.
Het Delicious Brains-artikel over WordPress-migraties en David Coveney's originele uitleg over de serialisatie-fix, geschreven door de auteur van het interconnect/it-script zelf, beschrijven allebei het exacte mechanisme in detail.
De oplossing is een tool die de data unserialised, de string vervangt, de byte-prefix herberekent en weer opnieuw serialised. Drie tools doen dit goed. Hieronder in volgorde van voorkeur.
Methode 1: WP-CLI wp search-replace (aanbevolen)
WP-CLI wp search-replace is de default als je SSH-toegang hebt tot de server. Het is de snelste optie, de meest flexibele, en degene die de meeste edge cases correct afhandelt. WP-CLI heeft ook een fatsoenlijke dry-run-mode, die de andere tools wel hebben maar minder transparant uitvoeren.
Stap 1: dry-run
Draai altijd eerst de dry-run. Die laat zien welke tabellen en kolommen aangeraakt zouden worden en hoeveel vervangingen er zouden plaatsvinden, zonder iets te schrijven. Vergelijk het aantal met wat je verwacht voordat je gaat committen.
# Dry-run: laat zien wat er zou veranderen, schrijft niets.
# --skip-columns=guid is verplicht, zie hieronder.
# --all-tables-with-prefix pakt ook plugin- en HPOS-tabellen mee.
wp search-replace 'https://old-domain.com' 'https://new-domain.com' \
--all-tables-with-prefix \
--skip-columns=guid \
--dry-run
Verwachte output: een tabel met elke verwerkte kolom en het aantal vervangingen per kolom, afgesloten met een regel zoals Success: 847 replacements to be made. Is het aantal nul? Dan staat de oude URL niet in de database (check de exacte string, inclusief protocol en of er wel of geen www. voor staat). Is het verdacht hoog? Dan matcht er iets anders ook op het patroon van de oude URL en moet je eerst uitzoeken wat, voordat je commit.
Stap 2: commit de replacement
Als de dry-run er goed uitziet, draai je hetzelfde commando zonder --dry-run:
# Commit: schrijft de wijzigingen daadwerkelijk weg.
wp search-replace 'https://old-domain.com' 'https://new-domain.com' \
--all-tables-with-prefix \
--skip-columns=guid
Verwachte output: dezelfde tabel als de dry-run, afgesloten met Success: 847 replacements to be made. Dat aantal moet exact overeenkomen met de dry-run. Komt het niet overeen, dan is er tussen de twee commando's iets in de database veranderd (een andere gebruiker die schreef, een cronjob, een plugin die iets deed). Zet in dat geval de backup terug en onderzoek wat er gebeurde voordat je een tweede poging waagt.
De drie flags die ertoe doen
--skip-columns=guid is niet-onderhandelbaar. De WordPress-verhuisdocumentatie is ondubbelzinnig: "Never, ever, change the contents of the GUID column, under any circumstances." GUIDs in wp_posts zijn permanente identifiers voor RSS-feedreaders, zodat die oude posts niet opnieuw als nieuw tonen. Verander je ze, dan zien alle RSS-abonnees alles opeens weer als nieuw. De flag bestaat precies om die kolom over te slaan.
--all-tables-with-prefix vervangt in elke tabel die met de WordPress-prefix begint (wp_ standaard), dus ook plugin-tabellen die niet geregistreerd zijn in het WordPress $wpdb object. Dit is de flag die WooCommerce HPOS-tabellen meepakt (wp_wc_orders, wp_wc_order_addresses, wp_wc_order_operational_data, wp_wc_orders_meta), Yoast indexable-tabellen, Wordfence scan-tabellen en vergelijkbaar. Zonder deze flag verwerkt WP-CLI alleen de tabellen die het uit $wpdb kent, en blijven je HPOS-orders naar het oude domein wijzen.
--precise (optioneel maar aan te raden bij migraties) dwingt WP-CLI om PHP-gebaseerde processing te gebruiken in plaats van het standaard SQL-snelle pad. Het SQL-pad schakelt automatisch over op PHP zodra het serialised data detecteert, en die detectie is gebaseerd op kolomtype-heuristiek. --precise haalt die heuristiek weg en laat elke rij door PHP gaan. Dat is langzamer, maar in elk geval correct. Bij databases onder een paar gigabyte merk je van die tijd niks.
# Migratie-variant met --precise.
wp search-replace 'https://old-domain.com' 'https://new-domain.com' \
--all-tables-with-prefix \
--skip-columns=guid \
--precise
Stap 3: werk siteurl en home expliciet bij
De rijen siteurl en home in wp_options bepalen waar WordPress denkt dat hij draait. De search-replace werkt ze bij, maar een veelvoorkomend probleem is dat ze gecachet zitten in een object cache, of als constante in wp-config.php zijn gezet. In beide gevallen is de database-rij niet meer de bron van waarheid. Werk ze daarom expliciet bij en flush daarna de cache, zodat de toestand eenduidig is:
# Belt-and-suspenders: werk de twee rijen expliciet bij na de replace.
wp option update home 'https://new-domain.com'
wp option update siteurl 'https://new-domain.com'
# Flush de object cache, voor het geval die nog oude waardes vasthoudt.
wp cache flush
Verwachte output: Success: Updated 'home' option. en Success: Updated 'siteurl' option. Krijg je Success: Value passed for 'home' is unchanged., dan had de search-replace dat al gedekt. Dat is het normale geval.
Methode 2: Better Search Replace plugin (als SSH niet beschikbaar is)
Als de host alleen een web-based filemanager biedt en geen SSH, dan is de Better Search Replace plugin de juiste tool. Hij wordt onderhouden door WP Engine, heeft meer dan een miljoen actieve installaties en is op moment van schrijven getest met WordPress 6.9.4. Versie 1.4.5 (januari 2024) voegde allowed_classes => false toe aan de unserialize-call naar aanleiding van een Wordfence security-melding. Houd de plugin dus bij.
- Installeer Better Search Replace via
Plugins > Nieuwe toevoegenen activeer de plugin. - Ga naar
Tools > Better Search Replace. - Vul bij
Search forde waardehttps://old-domain.comin en bijReplace withde waardehttps://new-domain.com. Geen trailing slash. - Selecteer bij
Select tablesalle tabellen in de lijst. Klik de eerste, scroll naar de laatste, shift-klik de laatste om alles te selecteren. - Laat
Case-Insensitive?uit. URLs zijn op netwerkniveau hoofdlettergevoelig, en deze flag geeft onvoorspelbare vervangingen. - Laat
Replace GUIDs?uit. Zelfde reden als de--skip-columns=guidflag in WP-CLI. - Vink
Run as dry run?aan en klikRun Search/Replace. Je krijgt een samenvatting: aantal doorzochte tabellen, aantal onderzochte cellen, aantal aangepaste cellen. Controleer of de aantallen kloppen. - Vink
Run as dry run?uit en klik nog een keerRun Search/Replaceom te committen.
Verifieer het resultaat: de samenvatting van de plugin moet hetzelfde aantal wijzigingen laten zien als de dry-run. Is dat meer of minder, dan is er tussen de runs iets in de database veranderd en zet je de backup terug voordat je verder gaat.
Better Search Replace handelt PHP serialised data correct af. Wat hij niet documenteert is of hij Gutenberg JSON-escaped URLs meeneemt, en dat brengt je direct bij de volgende sectie.
Een bekende valkuil in Better Search Replace
In een review op WordPress.org uit februari 2026 meldde iemand dat ongerelateerde content werd vervangen in een WooCommerce-installatie bij het zoeken op een korte string. De les is dezelfde als bij elke search-replace-tool: zoek op de volledige URL inclusief protocol, niet op een fragment, en draai altijd eerst de dry-run. Zoek je op example, dan matcht hij elke rij in de database die het woord example bevat, en dat wil je zelden.
Methode 3: Search Replace DB script (laatste redmiddel)
Het interconnect/it Search Replace DB-script is een standalone PHP-script dat rechtstreeks met de database praat en de replacement uitvoert. Het is de originele tool waar zowel WP-CLI's search-replace als Better Search Replace hun logica van afgeleid hebben. Versie 4.1.4 is uitgebracht op 11 april 2025 en voegde PHP 8.x-compatibiliteit toe. De broncode staat op GitHub.
Gebruik dit script alleen als WP-CLI niet beschikbaar is (de site is stuk en WordPress bootstrapt niet eens, of WP-CLI staat niet op de server) en als Better Search Replace niet geinstalleerd kan worden (geen admintoegang, omdat de admin ook stuk is). Die combinatie komt zelden voor.
De security-waarschuwingen zijn niet-onderhandelbaar:
- Het script biedt web-toegankelijke database-credentials en full read/write op elke tabel. Iedereen die de URL vindt kan hem brute-forcen of credentials scrapen.
- Upload het script naar een willekeurig benoemde map (
/xy7kq3z9/, niet/srdb/en ook niet de site-root). Die mapnaam is het enige wat random scanners tegenhoudt. - Verwijder de hele scriptmap direct na gebruik, op elke publiek bereikbare server, zonder uitzondering. De officiele pagina dwingt je een vinkje aan te zetten dat je dit snapt voordat je kunt downloaden.
- Draai het over HTTPS, zodat de database-credentials niet in plain text over het net gaan. Is HTTPS niet beschikbaar? Wijzig het databasewachtwoord direct na afloop.
- Voor maximale veiligheid raadt interconnect/it zelf aan om het script op een aparte server te draaien en de databaseconnectie over SSH te tunnelen, in plaats van hem op de target-host te zetten.
De interface van het script is een webformulier: vul de zoekstring, de replace-string, eventueel de database-credentials (als het script die niet uit wp-config.php kan lezen) in, kies tabellen en klik op Dry Run of Live Run. Er zit een Delete me-knop in die de scriptmap weer opruimt. Klik die knop na afloop, en controleer daarna via SSH of FTP of de map ook echt weg is.
De Gutenberg JSON-escape valkuil
Dit is de meest voorkomende productiebrekende edge case bij WordPress search-replace, en hij ontbreekt in vrijwel elke tutorial. Ook als je al honderd migraties met WP-CLI hebt gedaan en hem nog nooit bent tegengekomen: lezen.
Waarom het gebeurt
De Gutenberg-block-editor, default sinds WordPress 5.0 (december 2018), slaat blockcontent op in wp_posts.post_content als HTML met comment-delimiters waarin JSON-block-attributen zitten. Een image block ziet er zo uit in de database:
<!-- wp:image {"id":42,"url":"https:\/\/old-domain.com\/image.jpg","alt":"hero"} -->
<figure class="wp-block-image"><img src="https://old-domain.com/image.jpg" alt="hero"/></figure>
<!-- /wp:image -->
Let op de twee URL-vormen in hetzelfde block. De src in de gerenderde HTML bevat de gewone URL: https://old-domain.com/image.jpg. Het JSON-block-attribuut in de HTML-comment bevat de JSON-escaped variant: https:\/\/old-domain.com\/image.jpg. De forward slashes zijn geescaped als \/, omdat PHP's json_encode() ze standaard zo escapet.
Een normale wp search-replace 'https://old-domain.com' 'https://new-domain.com' vervangt de gewone vorm. Hij vervangt de escape-vorm niet. De afbeelding rendert gewoon op de front-end (de src is bijgewerkt), maar zodra je de post in de block-editor opent, leest Gutenberg de JSON-attributen, ziet de oude URL, en valt zonder foutmelding terug op ongestylede placeholders. Elk block-attribuut met een URL erin: links in een button block, embed blocks, cover blocks met een achtergrondafbeelding, gallery blocks, je loopt ertegenaan.
WP-CLI issue #5293 tracked dit als feature request. De issue is in oktober 2023 gesloten als "not planned", wat betekent dat wp-cli dit niet automatisch gaat afhandelen. De workaround is een expliciete tweede pass.
De tweede pass
Draai na de eerste search-replace een tweede voor de escaped vorm:
# Tweede pass: JSON-escaped URLs in Gutenberg block-attributen.
# Double quotes in bash zodat de backslashes letterlijk blijven.
wp search-replace "https:\/\/old-domain.com" "https:\/\/new-domain.com" \
--all-tables-with-prefix \
--skip-columns=guid
Verwachte output: Success: N replacements to be made., waarbij N het aantal block-attribuut-URLs in de database is. Op een site met veel Gutenberg-content kunnen dat er honderden zijn. Op een site die alleen de Classic Editor gebruikt is het meestal nul en is het commando een veilige no-op die je mag overslaan.
De shell-quoting is belangrijk. Gebruik double quotes in bash, dan geeft de shell de backslashes letterlijk door aan WP-CLI. Single quotes werken hier trouwens ook, omdat bash binnen single quotes helemaal niks interpreteert, maar double quotes is de veilige default.
Gebruikt de site een ongewone URL-vorm zoals https:\/\/ (escaped) binnen een shortcode of custom field? Dan kan een derde pass voor die specifieke vorm nodig zijn. Zoek dan eerst in de database naar \/\/ met een dry-run, om te zien wat er gematcht zou worden:
# Diagnostische dry-run om escaped URLs te vinden die je niet verwachtte.
wp search-replace 'old-domain.com' 'new-domain.com' \
--all-tables-with-prefix \
--skip-columns=guid \
--dry-run \
| grep -i "domain"
Doet Better Search Replace dit automatisch?
Per versie 1.4.10 (januari 2025) claimt de documentatie van Better Search Replace niet dat de plugin JSON-escaped URLs in Gutenberg-block-attributen afhandelt. De serialisatie-logica van de plugin richt zich op PHP serialised strings, niet op JSON-encoded strings binnen HTML-comments. In een test op een WordPress 6.7-site met Gutenberg-content liet de plugin \/ escaped URLs na een enkele run gewoon staan, hetzelfde gedrag als WP-CLI zonder tweede pass. Gebruik je Better Search Replace op een Gutenberg-heavy site? Draai dan nog een tweede pass op de escaped vorm.
Welke tabellen moeten bijgewerkt worden
Een veelvoorkomend misverstand is dat alleen wp_options bijgewerkt hoeft te worden. De tabel hieronder geeft elke standaard WordPress-tabel die hardcoded URLs kan bevatten, plus de gangbare plugin-tabellen die WooCommerce aanmaakt. De --all-tables-with-prefix flag dekt ze allemaal in een keer.
| Tabel | URLs erin? | Wat voor URLs |
|---|---|---|
wp_options |
Ja | siteurl, home, widget-instellingen, theme mods, plugin-opties (vaak serialised). |
wp_posts.post_content |
Ja | Content-body: image embeds, links, Gutenberg block-attributen, Classic Editor-markup. |
wp_posts.guid |
Ja, maar nooit vervangen | Permanente identifier voor RSS. Wordt overgeslagen door --skip-columns=guid. |
wp_postmeta |
Ja | Custom fields, featured image-URLs, pagebuilder-layouts (Elementor, Beaver Builder, Divi), SEO-plugin-metadata. Veel serialised content. |
wp_commentmeta |
Zelden | Bestaat sinds WordPress 2.9 (december 2009). Kan door plugins geplaatste URLs bevatten, maar meestal niet. Valt wel onder --all-tables-with-prefix. |
wp_termmeta |
Zelden | Bestaat sinds WordPress 4.4 (december 2015). Taxonomie-term-metadata; sommige SEO-plugins zetten hier URLs in. |
wp_usermeta |
Zelden | Gebruikersprofielen. Sommige plugins slaan avatar-URLs hier op. |
wp_links |
Ja | Legacy link-manager (standaard uit sinds WordPress 3.5, zit nog wel in het schema). |
wp_wc_orders |
Ja | WooCommerce HPOS-order-tabel, default sinds WooCommerce 8.2 (oktober 2023). Kan URLs uit ordermetadata bevatten. |
wp_wc_order_addresses |
Zelden | Facturatie- en verzendadressen. URLs zijn hier ongebruikelijk. |
wp_wc_order_operational_data |
Zelden | Operationele orderstaat. URLs zijn hier ongebruikelijk. |
wp_wc_orders_meta |
Ja | HPOS-order-meta. Plugins zetten hier download-URLs, payment-gateway-returns en refund-links in, vaak serialised. |
De oudere bewering dat wp_commentmeta en wp_termmeta zijn geintroduceerd in WordPress 5.3 is onjuist en duikt nog wel op in oudere tutorials. wp_commentmeta zit sinds WordPress 2.9 (2009) in het schema. wp_termmeta kwam erbij in WordPress 4.4 (2015). Beide bestaan dus ruim voor WordPress 5.3, dat pas in november 2019 uitkwam. Zegt een tutorial iets anders, dan klopt er waarschijnlijk meer niet.
De wp_posts.guid-waarschuwing, voor de laatste keer
De GUID-kolom verdient een eigen paragraaf, want niet voor niks staat die waarschuwing zo vaak herhaald. Het is een permanente identifier die RSS-feedreaders gebruiken om te bepalen of een post nieuw is. Zodra een GUID eenmaal gezet is voor een post, verandert hij nooit meer, ook niet als de URL van die post verandert. De WordPress-verhuisdocumentatie is ondubbelzinnig: "Never, ever, change the contents of the GUID column, under any circumstances." De kolom overslaan met --skip-columns=guid is de manier om te voorkomen dat je RSS breekt voor elke bestaande abonnee. Een search-replace die de GUID-kolom meepakt, zorgt ervoor dat elke RSS-reader ter wereld elke post van de site opeens als nieuw toont.
Het resultaat verifieren
Vertrouw na de replace niet blind op de success-melding. Controleer of de database de nieuwe URL ook echt bevat en dat er niks van de oude URL meer instaat:
# Tel hoeveel keer de oude URL nog voorkomt in de hele database.
wp db search 'old-domain.com' --all-tables-with-prefix | wc -l
Verwachte output: nul, of dichtbij nul. Wat overblijft zit ofwel in de guid kolom (prima, bedoeld, met opzet overgeslagen) ofwel in een tabel die niet door de run gedekt werd. Staan er nog steeds hits? Draai het commando zonder wc -l om te zien in welke rijen de oude URL zit en beslis of die een gerichte tweede pass nodig hebben.
Check daarna een paar URL-kritieke plekken via de UI:
- Laad de homepage. Afbeeldingen moeten laden; menu-items moeten naar het nieuwe domein wijzen.
- Log in op wp-admin. In de adresbalk moet het nieuwe domein staan.
Instellingen > Algemeenmoet de nieuwe URL laten zien in zowelWordPress AddressalsSite Address. - Open een post in de block-editor. Elke block moet zijn content tonen, niet een lege placeholder. Dit is de test die de Gutenberg JSON-escape-val pakt.
- Voor WooCommerce: open
WooCommerce > Bestellingen, klik door naar een recente bestelling en verifieer dat opgeslagen URLs (customer-facing order-URL, download-links) naar het nieuwe domein wijzen. - Ga naar
Instellingen > Permalinksen klik een keerWijzigingen opslaan, zonder iets te veranderen. Dit regenereert de.htaccess-rewrite-regels, die niet per se aangepast hoeven worden maar na een verhuizing soms toch op het oude domein blijven hangen.
Gaat een van die checks mis? De meest waarschijnlijke oorzaken: de Gutenberg-tweede-pass ontbrak, een object cache houdt oude waardes vast (wp cache flush en een harde browser-reload), of een plugin slaat URLs buiten de database op in een bestand op disk (zelden, maar Elementor's file cache is een voorbeeld: verwijder wp-content/uploads/elementor/css/ na een URL-wijziging en laat de plugin hem regenereren).
Mythes die weg mogen
Drie dingen die tutorials en forums roepen en die gewoon niet kloppen, voor de duidelijkheid:
"phpMyAdmin's REPLACE() is prima voor kleine sites." Nee, dat is het niet. Het serialised-data-probleem hangt niet van de grootte van de site af. De eerste rij met een serialised widget gaat stuk, en je merkt het pas een uur later als de widget niet meer rendert. Gebruik WP-CLI of Better Search Replace, ook op een brochuresite van vijf paginas.
"Alleen wp_options hoeft bijgewerkt." Echt niet. De wp_posts.post_content kolom bevat elke image-embed, link en Gutenberg-block-URL in elke post. wp_postmeta bevat Elementor-layouts, Beaver Builder-data, Advanced Custom Fields-waardes en Yoast SEO-metadata, allemaal vol URLs. WooCommerce HPOS zet orderdata in eigen tabellen. Alleen wp_options doen laat een kapotte site achter.
"Van staging naar productie heb je geen search-replace nodig als ze dezelfde URL hebben." Meestal wel. Het normale scenario is dat staging op staging.example.com draait en productie op example.com, en dan verschillen de URLs dus. De enige uitzondering is als staging via een lokale hosts-file-entry hetzelfde productiedomein gebruikt, en dat is ongebruikelijk. Zelfs dan moet je transients legen en siteurl en home verifieren. De WordPress-verhuisdocumentatie raadt aan om DELETE FROM wp_options WHERE option_name LIKE '%\_transient\_%' te draaien na elke migratie, of de URLs nou hetzelfde zijn of niet.
Complete eindconfiguratie
Hieronder de complete volgorde voor een WP-CLI-migratie van https://old-domain.com naar https://new-domain.com, met elke stap op de goede plek. Dit is de referentie die je in een runbook plakt.
# 1. Back-up de database voordat je iets aanraakt.
wp db export /tmp/pre-search-replace-$(date +%Y-%m-%d).sql
# 2. Dry-run van de primaire search-replace.
wp search-replace 'https://old-domain.com' 'https://new-domain.com' \
--all-tables-with-prefix \
--skip-columns=guid \
--dry-run
# 3. Commit de primaire search-replace.
wp search-replace 'https://old-domain.com' 'https://new-domain.com' \
--all-tables-with-prefix \
--skip-columns=guid \
--precise
# 4. Tweede pass: Gutenberg JSON-escaped URLs.
wp search-replace "https:\/\/old-domain.com" "https:\/\/new-domain.com" \
--all-tables-with-prefix \
--skip-columns=guid
# 5. Expliciete option-updates (belt-and-suspenders).
wp option update home 'https://new-domain.com'
wp option update siteurl 'https://new-domain.com'
# 6. Transients wissen.
wp transient delete --all
# 7. Object cache en rewrite rules flushen.
wp cache flush
wp rewrite flush
# 8. Verifieer: geen voorkomens meer van de oude URL (behalve in guid).
wp db search 'old-domain.com' --all-tables-with-prefix --skip-columns=guid
Voor multisite voeg je aan elk wp search-replace commando de flag --network toe, zodat hij ook per-site-tabellen pakt. Heeft de site non-prefixed plugin-tabellen (zelden, maar sommige legacy-plugins doen het), vervang dan --all-tables-with-prefix door --all-tables en kijk goed naar de dry-run-output, want --all-tables raakt ook non-WordPress-tabellen die de database delen.
Voor de stappen rond de search-replace heen (bestanden kopieren, database dumpen, DNS omzetten, testen via hosts-file) staat de volledige walkthrough in WordPress verhuizen naar een nieuwe host. Voor uitleg van de wp-config.php constanten die in dat artikel voorbijkomen, zie de wp-config.php-referentie.