How to migrate WordPress to a new host or domain

A complete, neutral WordPress migration guide covering files, database, URL search-replace, pre-launch testing on the new host, and a DNS switch that keeps downtime close to zero.

Moving a WordPress site to a new host is one of those tasks that looks simple on the surface and quietly hides a dozen ways to break. You copy the files, you import the database, you change the URL, and then half the menus vanish, the page builder shows placeholder boxes, images point at the old domain, and the contact form returns a 404. The problem is almost never the copy step. It is the steps around it: serialised PHP in the database, URL references embedded in post content and widget configuration, DNS cached by ISPs for days, and, for WooCommerce sites, order data in custom tables that a generic search-replace does not touch. This article walks through the full migration in three shapes (plugin, manual, and hybrid), with the failure modes and the mitigations in each one.

The scenario covered here is the most common one: a live site moving to a new host, possibly also changing its domain. The article is neutral about hosts because the migration steps do not depend on which host you are moving to. The only host-specific detail is the DNS record at the end, which you update in the registrar or the previous host's DNS, not on the new host.

In short

If you only remember four things from this article, remember these:

  1. A WordPress migration is three things, not one. It is a file copy, a database copy, and a URL update inside the database. If you only do the first two, the site breaks in ways that are hard to diagnose.
  2. Never run a plain SQL UPDATE on URLs in the database. WordPress stores menus, widgets, and page builder configuration as serialised PHP, and a raw SQL replace corrupts the length prefixes. Use WP-CLI wp search-replace (see the linked guide for the Gutenberg-aware method comparison) or(https://developer.wordpress.org/cli/commands/search-replace/) or Better Search Replace, both of which deserialise, replace, and recalculate the length prefix correctly.
  3. Test on the new host before the DNS change. The hosts file on your laptop lets you load the new server at the real domain while the rest of the world still sees the old site. Skip this step and your first visitors are the ones who discover the broken pages.
  4. Lower the DNS TTL 24 hours before the switch. DNS propagation is not instant and ISP resolvers can cache records for 24 to 48 hours even after the TTL expires. Dropping the TTL to 300 seconds a day before the migration cuts the worst case from two days to five minutes.

The rest of the article is the reasoning and the exact commands.

Table of contents

Migration overview: files, database, and DNS

A WordPress site is two things living in two places, plus a DNS record that tells browsers where to find them. A migration copies the first two and updates the third.

The files live on disk in the WordPress directory and include wp-content/ (uploads, themes, plugins, mu-plugins), wp-config.php, .htaccess, and the WordPress core files themselves. The core files can be reinstalled from scratch, but wp-content/ is yours. If you lose the uploads directory, you lose every image, PDF, and attachment that users ever uploaded.

The database is a MySQL or MariaDB database with roughly a dozen default tables plus one or more per installed plugin. It contains every post, page, comment, user, plugin setting (under wp_options), menu structure, widget configuration, and for WooCommerce sites, every order, customer, and stock level. The official WordPress migration guide is clear on the split: you cannot migrate files without the database, and you cannot migrate the database without the files.

DNS is the pointer. The domain has one or more A records (or AAAA for IPv6, or CNAME depending on the host) that resolve the domain name to an IP address. When you switch hosts, you point those records at the new IP. This is the only step that is visible to the outside world. Until you touch it, the rest of the internet still loads the old site.

The correct order is: copy files, copy database, update URLs inside the database, test on the new host using a local hosts file entry, and only then change DNS. Skipping the test step is the most common source of post-launch panic.

Prerequisites

You need all of these in place before you start. Walking off halfway because of a missing prerequisite is how small migrations become weekend migrations.

  • A fresh, verified backup of the live site (database plus files). Not yesterday's scheduled one. A fresh one, taken now, downloaded somewhere you can reach even if the site is down. See the WordPress backup strategy article for what a real backup contains and how to take one.
  • Access to both hosts: SFTP or SSH to the old host and the new host, plus the database credentials on both sides (or the ability to create a new database on the new host).
  • Access to DNS management for the domain, either at the registrar or wherever the authoritative nameservers live. If someone else manages DNS, get them on standby for the switch step.
  • The new host is provisioned with the same or newer PHP version as the live site (check Tools > Site Health on the old site). If the new host is on PHP 8.3 and the old one on PHP 7.4, read WordPress slow after update first because a PHP jump of two versions can expose plugin incompatibilities at the same moment as the migration.
  • WP-CLI 2.10 installed on the new host if you plan to use the command-line method, and SSH access to run it. WP-CLI is the cleanest way to run search-replace on large databases because it streams without timing out.
  • The new domain name (if you are changing it) pointed at a parking page or unused, so you can test against the new host using a local hosts file entry without interfering with the live DNS.
  • A list of commercial plugin license keys. Commercial plugins often bind to a domain, and moving the site may require a license re-activation on the new URL. Have the keys ready so you do not get stuck during launch.

Before you start: lower the DNS TTL

Do this step 24 hours before the migration, not on the day. DNS TTL (Time To Live) controls how long downstream resolvers cache a record. A typical default is 3600 seconds (one hour) or 14400 seconds (four hours). On the day of the switch, you want the TTL as low as possible so that when you change the A record, the world sees the change in minutes instead of hours.

  1. Log in to wherever your domain's DNS is managed (registrar, Cloudflare, or the old host's DNS panel).
  2. Find the A record (or AAAA for IPv6) for the root domain and the www subdomain.
  3. Change the TTL to 300 seconds (5 minutes) or the lowest value the interface allows.
  4. Save and wait at least as long as the previous TTL. If the old TTL was 14400, wait four hours. If it was 3600, wait an hour. This lets resolvers that cached the old TTL expire their cache and pick up the new, shorter TTL.

After the migration is complete and stable, raise the TTL back to something sensible (3600 or 14400) so resolvers cache your records normally again.

WPShout's migration guide is blunt about the reason: "ISPs can cache records for 24 or 48 hours or more" even after TTL-based expiry, because some ISPs honour TTL loosely or not at all. The pre-migration TTL drop is how you bound the worst case.

Method 1: Migration plugin (easiest)

For most small and medium sites, a migration plugin is the right choice. It bundles the files and database into a single archive on the old host and unpacks them on the new host, then runs a search-replace step on URLs. There are three plugins worth considering.

All-in-One WP Migration exports the entire site (database, files, plugins, themes) into a single .wpress file that you import on the new host. Free tier limits the import size to 512 MB on most hosts; the paid extension lifts that limit. It is the simplest UI of the three and the one to reach for on small sites.

Duplicator packages the site into two files: a .zip archive and an installer.php script. You upload both to the new host, load installer.php in a browser, and it rebuilds the site from the archive. Duplicator was purpose-built for migration and cloning, not backup, so its search-replace step is thorough and its UI is explicit about what it is doing. This is my default recommendation for sites under a few gigabytes.

Migrate Guru is BlogVault's free migration plugin and handles very large sites (multi-gigabyte) by streaming the archive through their servers. The tradeoff is that your site content passes through a third party during the migration; read their privacy policy and decide whether that is acceptable.

Steps (using Duplicator as the example)

  1. On the old site, install and activate the Duplicator plugin.
  2. Go to Duplicator > Packages > Create New and click through the wizard. On the Setup screen, leave the archive as .zip and the installer as installer.php. Click Next.
  3. Duplicator runs a scan. Fix any red warnings before proceeding. Common ones are "Site too large" (consider the manual method) and "MySQL stored procedures detected" (rare; usually safe to continue).
  4. Click Build. When the build finishes, you download two files: installer.php and a .zip archive with a long unique name.
  5. Create a database on the new host (via the host's control panel or mysql CLI). Note the database name, username, password, and host.
  6. SFTP or upload the two Duplicator files to the root of the new host's web directory (where wp-config.php would normally live). Make sure the directory is otherwise empty.
  7. In your browser, load https://new-server-ip/installer.php (or the new domain if it already points somewhere, or a temporary URL the new host provided). The installer page appears.
  8. Paste the database credentials from step 5 into the installer. Check Test Database. Proceed when the test passes.
  9. On the next screen, confirm the old URL and new URL. Duplicator uses this for its search-replace step. If the domain is not changing, the two URLs are the same.
  10. Run the installer. It extracts the archive, imports the database, and runs search-replace on URLs. When it finishes, it prompts you to log in and delete the installer files. Do both before moving on.

Expected output: The new host now has a working WordPress installation that matches the old one. At this point you cannot yet visit it at the real domain because DNS still points at the old host. That is the hosts file test step, covered below.

Verify the final result: Follow Duplicator's prompts to delete installer.php, the archive, and any other installer leftovers from the web root. They are a security risk if left in place. Then run the tests in the Testing on the new host before going live section.

When the migration plugin is the wrong choice

Migration plugins break on very large sites (multi-gigabyte databases or hundreds of gigabytes of uploads), sites behind restrictive server configuration (low PHP memory, short max_execution_time), and sites where the old host has already become unreachable. In any of those cases, go to Method 2.

Method 2: Manual migration (most control)

The manual method treats a migration as what it is: a file copy and a database dump. It needs shell access to both hosts, but it is the most reliable path for large sites and the only path when a plugin cannot run.

Step 1: Dump the database on the old host

SSH into the old host and cd to the WordPress root (the directory containing wp-config.php). Export the database with WP-CLI if it is available, or mysqldump if it is not.

# Using WP-CLI (preferred): picks up credentials from wp-config.php automatically.
# --add-drop-table makes the dump restorable over an existing database.
wp db export - --add-drop-table | gzip > /tmp/old-site-db-$(date +%F).sql.gz
# Using mysqldump: substitute the credentials from wp-config.php.
# --single-transaction avoids locking tables on InnoDB, --quick streams rows.
mysqldump --single-transaction --quick --add-drop-table \
  -h localhost -u wp_user -p wp_database \
  | gzip > /tmp/old-site-db-$(date +%F).sql.gz

Expected output: A gzipped SQL file in /tmp/ with a size between a few hundred KB and a few hundred MB, depending on the site. Verify the file is not empty with ls -lh /tmp/old-site-db-*.sql.gz and that it starts with a proper SQL header with zcat /tmp/old-site-db-*.sql.gz | head -5.

Step 2: Copy the files

Copy the full WordPress directory tree to the new host. The fastest path is rsync over SSH directly between the two servers (if the old host allows outbound SSH). Otherwise, rsync down to your laptop, then up to the new host.

# Direct old-to-new rsync, run on the old host.
# -a preserves permissions, -v is verbose, -z compresses in transit,
# -P shows progress and allows resume on interrupted transfers.
# --exclude drops caches and the dump just written.
rsync -avzP \
  --exclude='wp-content/cache' \
  --exclude='wp-content/uploads/cache' \
  --exclude='.git' \
  /var/www/old-site/ \
  deploy@new-host.example.com:/var/www/new-site/
# Fallback: rsync the site to your laptop, then up to the new host.
rsync -avzP old-host.example.com:/var/www/old-site/ ./old-site/
rsync -avzP ./old-site/ new-host.example.com:/var/www/new-site/

Expected output: A full copy of the WordPress directory on the new host. Verify with ls /var/www/new-site/ that the top-level wp-config.php, wp-content/, wp-includes/, and wp-admin/ are all present. Run du -sh /var/www/new-site/ and compare against the old host; the sizes should match within a few MB (small differences are cache files that were excluded).

A note about the -scaled suffix on images. WordPress 5.3 (November 2019) introduced the Big Image Threshold: images uploaded at larger than 2560 pixels on their longest edge get a scaled variant written to disk with a -scaled suffix (e.g., photo.jpg becomes photo-scaled.jpg), and the original filename is stored in the image meta under original_image. Both files must transfer in the rsync. If your rsync excludes either, wp_get_original_image_path() breaks for future uploads. The default rsync -a flag preserves everything and does not need special handling; the risk is only if you wrote a manual --include/--exclude list that accidentally filtered out one of the variants.

Step 3: Create the database on the new host and import the dump

SSH into the new host. Create an empty database and a user for it, then import the dump.

# Create database and user (substitute real values).
# Run this as the MySQL root user or an account with CREATE privileges.
mysql -u root -p <<'SQL'
CREATE DATABASE new_site_wp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'new_wp_user'@'localhost' IDENTIFIED BY 'a-long-random-password';
GRANT ALL PRIVILEGES ON new_site_wp.* TO 'new_wp_user'@'localhost';
FLUSH PRIVILEGES;
SQL
# Import the dump. WP-CLI route (preferred, picks up credentials from wp-config.php
# once you have edited the new host's wp-config.php. Edit that first, see below.
cd /var/www/new-site
zcat /path/to/old-site-db-2026-04-08.sql.gz | wp db import -
# Fallback: import via mysql CLI directly.
zcat /path/to/old-site-db-2026-04-08.sql.gz | mysql -u new_wp_user -p new_site_wp

Before running the WP-CLI import, edit /var/www/new-site/wp-config.php on the new host and update DB_NAME, DB_USER, DB_PASSWORD, and DB_HOST to the new values from the CREATE DATABASE block above. Leave the AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, and NONCE_KEY constants alone; changing them logs every user out, which is usually fine for a migration but is a deliberate choice, not an accident.

Expected output: wp db import prints nothing on success. mysql prints nothing on success. Verify the import with wp db query 'SHOW TABLES;' (WP-CLI) or mysql -u new_wp_user -p new_site_wp -e 'SHOW TABLES;'. You should see the full set of WordPress tables (wp_options, wp_posts, wp_users, etc.), plus any plugin-specific tables.

Verify the final result: Run wp core verify-checksums on the new host. It should report Success: WordPress installation verifies against checksums. If it reports mismatched files, either the file copy was incomplete or the old site had core files that differ from the official release (check for custom patches or a compromised old site before proceeding).

The critical step: search-replace URLs in the database

This is the step that a surprising number of tutorials skip or get wrong. The database just imported contains the old site's URL hardcoded in dozens of places: the siteurl and home rows of wp_options, hundreds of wp_posts rows where post content links to other pages or embeds images at the old URL, wp_postmeta rows where plugin settings reference URLs, widget configuration, menu item URLs, Customizer settings, and for page builders like Elementor, Beaver Builder, and Divi, the entire page layout serialised as PHP with URL references inside.

Why a plain SQL UPDATE breaks everything

WordPress stores a lot of its configuration as serialised PHP, which is the output format of PHP's serialize() function. A serialised array looks like this:

a:2:{s:4:"home";s:22:"https://old-domain.com";s:7:"siteurl";s:22:"https://old-domain.com";}

The s:22 prefix means "the next string is 22 characters long". If you run a plain SQL UPDATE to replace https://old-domain.com with https://new-domain.com (24 characters instead of 22), the string value changes but the s:22 prefix does not. PHP's unserialize() reads the prefix, tries to read 22 bytes, gets 24 bytes of data that do not match the expected format, and fails silently. The result is menus that disappear, widgets that stop rendering, plugin settings that reset to default, and page builder layouts that render as empty divs.

The WP-CLI search-replace documentation and the Better Search Replace plugin both handle this correctly by deserialising, performing the string replacement, and recalculating the length prefix before re-serialising. Never run a raw SQL UPDATE against URLs in a WordPress database. Use one of the tools below.

Option A: WP-CLI (preferred for SSH access)

# Dry run first: shows what would change without modifying the database.
# --skip-columns=guid is mandatory. Never modify the GUID column, ever.
wp search-replace 'https://old-domain.com' 'https://new-domain.com' \
  --skip-columns=guid \
  --dry-run
# Real run: commits the changes. Run this only after reading the dry-run output.
# --all-tables-with-prefix includes plugin tables like WooCommerce HPOS.
# --precise forces PHP-based replacement for correct serialised data handling.
wp search-replace 'https://old-domain.com' 'https://new-domain.com' \
  --skip-columns=guid \
  --all-tables-with-prefix \
  --precise

Expected output: A table of tables and columns processed, ending with Success: Made N replacements. The dry run and the real run should report the same number of replacements; if the real run reports more or fewer, something changed in the database between the two runs and you should investigate before trusting the result.

Three flags matter here:

  • --skip-columns=guid is non-negotiable. The WordPress migration documentation is explicit: "Never, ever, change the contents of the GUID column, under any circumstances." GUIDs are meant to be permanent identifiers for RSS feed readers so they do not re-display old posts as new. Changing them causes every RSS reader to show every post as new again.
  • --all-tables-with-prefix includes all tables whose name starts with the WordPress table prefix (wp_ by default), which catches plugin tables that wp search-replace otherwise skips. For multisite, use --network instead to catch per-site tables.
  • --precise forces WP-CLI to use PHP-based search-replace (slower but correct) instead of the default SQL path that auto-switches to PHP only when it detects serialised data. On migrations, use --precise to be safe; the time penalty is negligible for typical sites.

Option B: Better Search Replace plugin (when SSH is not available)

If the new host only provides a web-based file manager and no SSH access, install and activate the Better Search Replace plugin on the new host, then:

  1. Go to Tools > Better Search Replace.
  2. In Search for, enter https://old-domain.com. In Replace with, enter https://new-domain.com.
  3. In Select tables, select all tables (use Ctrl+A or Cmd+A in the multi-select).
  4. Check Run as dry run? and click Run Search/Replace. Read the output.
  5. If the dry run looks correct, uncheck Run as dry run? and click Run Search/Replace again to commit.
  6. Flush caches (wp cache flush from CLI if available, or deactivate/reactivate caching plugins from the WordPress dashboard).

Better Search Replace is built on top of interconnect/it's Search Replace DB script, with modifications to use WordPress's native database functions. It handles serialised data correctly and is the recommended browser-based option.

After the search-replace: flush caches and regenerate .htaccess

Two cleanup steps that are easy to forget:

# Flush any object cache that might still contain old URLs.
wp cache flush

Then, in the WordPress dashboard, go to Settings > Permalinks and click Save Changes twice (once without changes, once again to be safe). This regenerates the .htaccess file with rewrite rules that match the new URL structure. The Delicious Brains migration guide calls this out explicitly because it is easy to miss and causes 404s on pretty permalinks after migration.

WooCommerce and HPOS: the tables search-replace misses

If the site runs WooCommerce, there is one extra step that catches nearly every migration and nearly no tutorial mentions. WooCommerce 8.2 (October 2023) made High-Performance Order Storage (HPOS) the default for new installations. HPOS moves order data out of wp_postmeta into four custom tables: wp_wc_orders, wp_wc_order_addresses, wp_wc_order_operational_data, and wp_wc_orders_meta.

WP-CLI's default wp search-replace without --all-tables-with-prefix or --all-tables does not touch these HPOS tables. It operates on the WordPress core tables and stops there. The result is that a migrated WooCommerce site can look fine on the front end but have every existing order's URLs still pointing at the old domain inside the HPOS tables, breaking order confirmation emails, customer account order links, and any WooCommerce reporting that embeds URLs.

The fix is to include --all-tables-with-prefix (which covers all wp_-prefixed tables including HPOS) or --all-tables (which covers everything in the database, even tables without the standard prefix):

# WooCommerce HPOS-aware search-replace.
# --all-tables-with-prefix catches the four _wc_order* tables alongside standard WP tables.
wp search-replace 'https://old-domain.com' 'https://new-domain.com' \
  --skip-columns=guid \
  --all-tables-with-prefix \
  --precise \
  --dry-run

To verify HPOS is enabled on the site: wp option get woocommerce_custom_orders_table_enabled. If it returns yes, HPOS is active and the --all-tables-with-prefix flag is required.

One more caveat. Elementor, Beaver Builder, and Divi all store their page layouts in a heavily nested serialised format inside wp_postmeta. WP-CLI's --precise flag handles most of it correctly, but Elementor specifically stores some data in a JSON-encoded format that has its own length prefixes. After the search-replace, go to Elementor > Tools > Replace URL in the WordPress dashboard and run a second URL replacement there. This is a quirk of Elementor, not a failure of WP-CLI, and is documented on the Elementor site.

Testing on the new host before going live

For a permanent staging setup rather than a one-off migration test, see the WordPress staging environment guide.

Before you touch DNS, test the migrated site at its real domain using your local hosts file. This lets your browser resolve the domain to the new server IP while the rest of the world still resolves it to the old server. You can click through the entire site, log in to wp-admin, test forms, and find broken links without any visitor seeing what you see.

Edit your hosts file

The hosts file is a plain text file that the operating system consults before DNS. Adding a line to it overrides DNS for your machine only.

On macOS and Linux: edit /etc/hosts with sudo nano /etc/hosts (or your editor of choice). Add a line:

123.45.67.89    yoursite.nl www.yoursite.nl

Replace 123.45.67.89 with the new host's IP address (ask the new host, or look at their control panel). Save and close.

On Windows: open Notepad as Administrator and edit C:\Windows\System32\drivers\etc\hosts. Add the same line format. Save.

Flush the local DNS cache so the change takes effect immediately:

  • macOS: sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
  • Linux (systemd-resolved): sudo systemd-resolve --flush-caches
  • Windows: ipconfig /flushdns in an admin cmd

Test the migrated site

  1. Open a new browser window (or better, a private/incognito one to avoid cached responses) and navigate to https://yoursite.nl. You should see the new host's copy of the site, not the old one.
  2. Verify you are actually hitting the new host. A reliable way is to create a small marker file on the new host only: echo 'new host' > /var/www/new-site/migration-marker.txt, then visit https://yoursite.nl/migration-marker.txt. If it returns new host, you are on the new server. If it 404s or returns something else, your hosts file edit is not taking effect (check the file, flush DNS cache, try a different browser).
  3. Click through the front page, a blog post, a category archive, a page built with your page builder, the search results page, and the 404 page. Images should load. Menus should work. Links should resolve to other pages on the site, not the old domain.
  4. Log in to https://yoursite.nl/wp-admin/. The login should work with your existing credentials. If it fails, clear the cookies for the domain (the hosts file entry plus old cookies sometimes produce a redirect loop; see the WordPress login redirect loop article).
  5. Once in wp-admin, visit Settings > General and confirm WordPress Address (URL) and Site Address (URL) both show the new domain. Visit Tools > Site Health and read any critical warnings.
  6. Test any contact forms or ecommerce flows end-to-end. For WooCommerce, place a test order using the cod (cash on delivery) gateway or a sandbox Stripe key. Do not rely on "the forms look okay" as proof; submit them.
  7. Delete the marker file: rm /var/www/new-site/migration-marker.txt.

Verify the final result: After steps 1 through 6, you have proven that the new host serves the site correctly at its real domain. The only thing still pointing at the old host is DNS, which is about to change.

Test from your phone on mobile data (not wifi). Your phone is not using the laptop's hosts file, so it is still seeing the old site. This is what the rest of the world sees right now. Use this view to decide whether any last-minute content changes on the old site (a blog post about to publish, a pricing update) need to be made before the DNS switch, and whether you need to put the old site into a maintenance mode to freeze its state.

Switching DNS with minimal downtime

With the TTL already dropped to 300 seconds (from the pre-migration step), the actual DNS switch is anticlimactic: change the A record to the new IP, wait 5 to 10 minutes, and the site is live.

  1. In your DNS management panel, find the A record for the root domain (@ or blank name) and the www subdomain.
  2. Change the IP address to the new host's IP. Keep the TTL at 300.
  3. Save. Some panels require an explicit "apply" or "publish" step. Make sure the change is committed.
  4. Wait a few minutes. In a private browser window (not the one with the hosts file entry), visit https://yoursite.nl. You should see the new site. If you still see the old one, you are probably hitting a cached DNS response. Try from a phone on mobile data, or use dig yoursite.nl @1.1.1.1 +short to query Cloudflare's public resolver directly; it should return the new IP.
  5. Remove the hosts file entry on your laptop now that DNS is correct. If you leave it in place, you will not notice when DNS later changes, which becomes a problem next time you migrate anything.
  6. Watch the old host's access log for incoming traffic. Requests will keep trickling in for hours because of cached DNS at various ISPs, but the bulk of traffic should shift within 10 to 20 minutes with a 300-second TTL.

Do not take the old site down yet

Leave the old host running for at least 48 hours after the DNS switch. Some ISPs cache DNS responses long past the TTL and will keep resolving the domain to the old IP for hours or days. If the old host goes dark immediately, those users see a dead site. Leaving it running means they still see the (now slightly stale) old site until their DNS cache expires, which is a much better experience than a failed connection.

A practical refinement: during those 48 hours, consider pointing the old host at a read-only, lightly-modified version of the site that shows a "site has moved" banner. This is optional, but it keeps the user experience sane and avoids the confusing situation where two people viewing the same URL see two different sites.

After 48 to 72 hours, the old host is safe to decommission. Make a final backup of the old database and files before deleting anything, and keep that backup somewhere you control for at least 30 days as an insurance policy against anything the migration missed.

Post-migration checklist

Run through this list in the first hour after the DNS switch. It catches the things that are easy to miss in the rush of "it's live".

  • Search Console: in Google Search Console, add the new domain as a property (if it changed) and submit the sitemap. If the domain changed, use the Change of Address tool to tell Google about the move.

  • 301 redirects: if the domain changed, set up permanent 301 redirects from the old domain to the new one on the old host. Every URL on the old domain should redirect to the equivalent URL on the new domain. This preserves the PageRank/SEO equity of existing inbound links. In .htaccess on the old host:

    # Permanent redirect everything from old-domain.com to new-domain.com.
    RewriteEngine On
    RewriteCond %{HTTP_HOST} ^(www\.)?old-domain\.com$ [NC]
    RewriteRule ^(.*)$ https://new-domain.com/$1 [R=301,L]
  • SSL/TLS certificate: verify the new host has a valid certificate for the domain. Most managed hosts auto-provision Let's Encrypt certificates; on a VPS, run certbot --nginx or equivalent. Visit the site and check the padlock is present.

  • Email: if your domain's MX records point at the old host's mail server, email now breaks. Update MX records separately (email DNS is independent of the web A record). If email was at a third party (Google Workspace, Fastmail), leave MX alone and only touch the A record.

  • wp-cron: verify wp-cron.php still runs. wp cron event list should show upcoming events. If a hosting-level cron was configured on the old host (typical on managed WordPress plans), recreate it on the new host.

  • Caching and CDN: if you use a CDN (Cloudflare, BunnyCDN, Fastly), clear its cache. If you use a caching plugin, clear its cache. Stale cache is a common reason the site "looks broken" after a migration when the files and database are actually correct.

  • robots.txt: check https://yoursite.nl/robots.txt returns the expected content. If the old host had a custom robots.txt that was generated rather than a static file, it may not have transferred.

  • Contact form smoke test: submit a real test message via the site's main contact form and verify you receive the email. Contact form plugins sometimes reset SMTP configuration during migration. See WordPress not sending email if mail is broken.

  • Search functionality: run a search query on the site. If search indexing is done by a plugin (Relevanssi, SearchWP), the index may need to be rebuilt from the plugin's settings.

  • Raise the DNS TTL: now that the migration is stable, raise the TTL back to 3600 or 14400 so resolvers cache your records normally.

Common myths worth burning

A short list of widely repeated migration myths that break real sites:

"Changing siteurl and home in the database is all you need." No. The WordPress migration docs list wp_options as the starting point and go on to specify updates across wp_posts, wp_postmeta, and for multisite, wp_site and wp_blogs. Updating only siteurl and home leaves the old URL embedded in thousands of posts, widgets, menus, and page builder layouts.

"Migration plugins handle serialised data automatically." Only when the plugin is built for migration and advertises serialised data support. Better Search Replace, WP-CLI search-replace --precise, and Duplicator do. A generic backup plugin that exports a SQL dump and imports it on the other side without a PHP-aware search-replace step does not. The WP Engine serialised data guide explains why: a raw SQL replace corrupts the length prefixes.

"DNS propagation is instant." ISPs cache DNS responses for 24 to 48 hours or longer, sometimes in defiance of the TTL you set. The only mitigation is to drop the TTL 24 hours before the migration so that resolvers which honour TTL see the short one and expire their cache quickly. The rest of the world catches up when they catch up.

"I can just change the URL in wp_options with a SQL UPDATE." You can, and the site will boot, and the first time you visit a page built with Elementor or a widget-heavy sidebar, it will render as empty divs or throw a PHP warning in the error log. Serialised data has to be unserialised, replaced, and re-serialised with correct length prefixes. Raw SQL cannot do this.

"I don't need a backup because I'm just copying the files." The moment something goes wrong halfway through the migration, the thing that saves you is a backup of the old site taken before you started. Not the snapshot from the old host's backup service (which may not cover mid-migration state). A fresh one, taken by you, stored somewhere not on either server. The WordPress backup strategy article has the details.

Where to go next

A migration is one of a handful of moments when the structure of a WordPress site is exposed, and it is a good time to address the things that were only half-working on the old host. Start with the WordPress security hardening article to lock down the new install before DNS switches, because a fresh server with default configuration and a known domain is a tempting target. If the migration is motivated by performance, read high TTFB in WordPress to make sure the new host actually solves the problem before you commit. And if the migration itself went wrong and the site now shows a white screen or a critical error, the recovery path is in there has been a critical error on this website.

Want WordPress hosting that stays stable?

I handle updates, backups, and security, and keep performance steady—so outages and slowdowns don't keep coming back.

Explore WordPress maintenance

Search this site

Start typing to search, or browse the knowledge base and blog.