How to change WordPress domain: Database & URL migration guide
EN

How to change WordPress domain: Database & URL migration guide

Last verified: June 29, 2026
11 min read
Guide
Full-stack developer

#How to Change WordPress Domain: Database & URL Migration Guide

Migrating a WordPress website to a new domain name seems straightforward in theory: you copy the filesystem, import the SQL database dump into a new target schema, and update the site and home URLs. In practice, executing this operation without serialization-aware tools will corrupt your database. Because WordPress stores options, widget parameters, and builder templates as serialized PHP strings, simply replacing domain names via standard SQL commands breaks layout configurations. This guide explains why serialization failures occur and provides a developer’s blueprint for executing migrations using WP-CLI, server-level 301 redirects, and SEO validations.

Learn more about our website migration services to Astro and Next.js to design fast, modern architectures.

Applying these domain migration strategies requires a systematic approach that balances database integrity with search visibility. Here is how to execute each strategy effectively.


#1. The Core Problem: PHP Serialization and String-Length Constraints

WordPress stores many configuration options, widget layouts, and theme properties inside the database table wp_options as serialized arrays or objects. A serialized PHP string explicitly defines the data type and the byte length of string values.

#The Anatomy of Serialized Strings

Consider this sample serialized string storing a domain setting: s:25:"https://old-domain.com";

In this structure:

  • s declares the variable type as a String.
  • 25 indicates that the string value contains exactly 25 characters.
  • "https://old-domain.com" is the actual string value.

#The Corruption Process and Class Namespace Mappings

If you run a standard SQL REPLACE query to change the domain name (for example, replacing old-domain.com with a longer domain like my-new-company-domain.com which is 27 characters long), the query updates the value but leaves the length indicator unchanged: s:25:"https://my-new-company-domain.com";

When WordPress retrieves this row from the database and runs the PHP function maybe_unserialize(), the parser sees the s:25 indicator, counts exactly 25 characters (stopping at n), and encounters a syntax error. The parser fails, returns false, and WordPress drops the option entirely.

Additionally, if a serialized string represents an instantiated class object: O:31:"WPPoland\Database\CustomObject":1:{...}

The parser expects to map the string definition to a declared class structure in memory. If a migration also renames namespaces or class definitions, and the original class definition is not loaded at runtime, PHP will instantiate a placeholder object of type __PHP_Incomplete_Class. This blocks method execution and causes severe application errors. Always manage migrations using serialization-aware workflows.


#2. Migrating via WP-CLI: The Command-Line Standard

The official WordPress Command Line Interface (WP-CLI) provides a built-in search-replace tool that is fully serialization-aware. This is the standard method for database migrations.

#Step 1: Pre-Migration Backup

Before making any database modifications, export a full SQL backup file:

# Export the database to a SQL file
wp db export backup-pre-domain-migration.sql

#Step 2: Running a Dry Run Simulation

Always execute a simulation using the --dry-run flag first. This allows you to audit the planned changes and verify the tables and columns that will be updated without modifying database rows.

Using the --precise flag forces WP-CLI to process each row using a precise PHP-based search-replace parser rather than relying on MySQL SQL wildcards. This is critical for data integrity because it guarantees that serialized objects are correctly parsed and reconstructed. If you omit the --precise flag, the engine may fall back to faster, non-serialization-aware SQL replace patterns on columns it assumes do not hold serialized arrays, which can corrupt custom layouts:

# Run a dry run replacement search
wp search-replace 'https://old-domain.com' 'https://new-domain.com' --all-tables --precise --dry-run

This output will display the exact number of replacements scheduled for each table:

+-------------------+---------+---------+
| Table             | Column  | Replace |
+-------------------+---------+---------+
| wp_options        | 42      | 42      |
| wp_posts          | 120     | 120     |
| wp_postmeta       | 75      | 75      |
| wp_comments       | 8       | 8       |
+-------------------+---------+---------+

#Step 3: Executing the Replaces and Performance Tuning

If the dry run statistics are correct, execute the actual replacements. To speed up the process on large databases (e.g., sites with millions of rows in wp_postmeta), use the --skip-columns flag to bypass logs and transient data:

# Execute search-and-replace, skipping log and redirect tables
wp search-replace 'https://old-domain.com' 'https://new-domain.com' --all-tables --precise --skip-columns=log_content,redirects_history

#Step 4: Resolving Mixed Content Issues

If you are migrating the site from HTTP to HTTPS at the same time, execute a second run to update any hardcoded insecure references:

# Replace legacy HTTP links with secure HTTPS links
wp search-replace 'http://old-domain.com' 'https://new-domain.com' --all-tables --precise

#Step 5: Post-Replacement Cache Clearing

After updating your database URLs, flush your permalinks and clear the object cache:

# Regenerate rewrite rules
wp rewrite flush

# Clear the object cache
wp cache flush

#3. Dynamic wp-config.php URL Overrides

If your database holds values for the old domain but you need to load the site under a temporary domain for testing, you can override these options dynamically inside wp-config.php.

#Implementation of Staging Overrides

Adding URL overrides to wp-config.php temporarily redirects site routing and resource loading. However, it does not alter the actual database rows. If you leave these overrides active permanently, the site will render, but any links embedded inside post content, page metadata, or custom plugin configurations will still point to the old domain. This creates mixed content errors and broken internal paths. Therefore, you should use these overrides only as a temporary measure to gain admin dashboard access, and always execute a complete database search-and-replace operation before launching the site:

// Hardcode site URLs temporarily to bypass database options
define( 'WP_HOME', 'https://new-domain.com' );
define( 'WP_SITEURL', 'https://new-domain.com' );

#Dynamic Multi-Environment Configuration

To support local development, staging servers, and production environments without changing configurations manually, set these properties dynamically based on the active server host:

// Define site URLs dynamically based on the current hostname
if ( isset( $_SERVER['HTTP_HOST'] ) ) {
    $protocol = ( isset( $_SERVER['HTTPS'] ) && 'on' === $_SERVER['HTTPS'] ) ? 'https' : 'http';
    define( 'WP_HOME', $protocol . '://' . $_SERVER['HTTP_HOST'] );
    define( 'WP_SITEURL', $protocol . '://' . $_SERVER['HTTP_HOST'] );
}

Note: Overriding site URLs inside wp-config.php disables the corresponding inputs on the Settings > General screen in the WordPress admin panel.


#4. Custom Serialization-Aware Search-Replace Script

If you need to perform migrations programmatically inside custom PHP scripts (e.g., when building automated deployment pipelines) without access to WP-CLI, use this custom class. It recursively parses arrays and objects, executes replacements, and updates string-length indicators.

Create this script in your theme directory as scripts/class-serialization-replace.php:

<?php
declare(strict_types=1);

namespace WPPoland\Database\Migrator;

class SerializationAwareReplacer {

    private string $search;
    private string $replace;

    public function __construct( string $search, string $replace ) {
        $this->search  = $search;
        $this->replace = $replace;
    }

    /**
     * Entry point to recursively search and replace strings in data.
     */
    public function process( $data ) {
        if ( is_string( $data ) ) {
            // Check if string is serialized
            if ( $this->is_serialized_string( $data ) ) {
                $unserialized = unserialize( $data );
                if ( false !== $unserialized ) {
                    return serialize( $this->process( $unserialized ) );
                }
            }
            return str_replace( $this->search, $this->replace, $data );
        }

        if ( is_array( $data ) ) {
            $cleaned = [];
            foreach ( $data as $key => $value ) {
                $cleaned[ $key ] = $this->process( $value );
            }
            return $cleaned;
        }

        if ( is_object( $data ) ) {
            $cleaned = clone $data;
            foreach ( get_object_vars( $data ) as $key => $value ) {
                $cleaned->$key = $this->process( $value );
            }
            return $cleaned;
        }

        return $data;
    }

    /**
     * Checks if a string is serialized.
     */
    private function is_serialized_string( string $string ): bool {
        $string = trim( $string );
        if ( 'N;' === $string ) {
            return true;
        }
        if ( strlen( $string ) < 4 ) {
            return false;
        }
        if ( ':' !== $string[1] ) {
            return false;
        }
        $last_char = substr( $string, -1 );
        if ( ';' !== $last_char && '}' !== $last_char ) {
            return false;
        }
        $token = $string[0];
        switch ( $token ) {
            case 's':
                if ( '"' !== substr( $string, -2, 1 ) ) {
                    return false;
                }
                return true;
            case 'a':
            case 'O':
                return (bool) preg_match( "/^{$token}:[0-9]+:/s", $string );
            case 'b':
            case 'i':
            case 'd':
                return (bool) preg_match( "/^{$token}:[0-9.E-]+;\$/", $string );
        }
        return false;
    }
}

#5. Web Server Permanent 301 Redirect Rules and Edge Redirects

To preserve search authority and redirect visitors from the old domain to the new one, configure 301 permanent redirects at the web server level.

#Nginx Configuration: Dynamic Wildcard Redirects

Nginx is highly efficient at handling redirects because it processes headers in memory before spawning a PHP worker process. When configuring redirects, always preserve the request URI (using $request_uri) and query string arguments. If you redirect all requests to the homepage of the new domain, search engines will treat this as soft 404 errors, causing all accumulated page authority to be lost.

Add this server block to your Nginx site configuration file:

server {
    listen 80;
    listen 443 ssl;
    server_name old-domain.com www.old-domain.com;

    # Configure SSL parameters here...

    # Perform permanent redirect preserving path and query strings
    return 301 https://new-domain.com$request_uri;
}

#Apache Configuration: .htaccess Directives

Add these rewrite rules to the top of the .htaccess file on your old domain’s server:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTP_HOST} ^(?:www\.)?old-domain\.com$ [NC]
  RewriteRule ^(.*)$ https://new-domain.com/$1 [R=301,L]
</IfModule>

#Cloudflare Workers: Edge-Level Redirects

To minimize server load and speed up redirects for global users, deploy this JavaScript script to a Cloudflare Worker on your old domain:

addEventListener('fetch', event => {
  event.respondWith(handleRedirectRequest(event.request))
})

async function handleRedirectRequest(request) {
  const url = new URL(request.url)
  // Map old path directly to the new domain
  const redirectUrl = `https://new-domain.com${url.pathname}${url.search}`
  
  return new Response('', {
    status: 301,
    headers: {
      'Location': redirectUrl,
      'Cache-Control': 'public, max-age=31536000'
    }
  })
}

Deploying redirects to edge servers reduces latency to under 15ms globally, preventing search engine crawler timeouts and conserving server bandwidth.


#6. SEO Migration Checklist and Google Search Console Setup

When changing domain names, you must notify search engines to transfer rankings and index pages under the new address.

#The GSC Change of Address Tool Workflow

  1. Verify Ownership: Verify both the old and new domain properties inside Google Search Console.
  2. Configure Redirects: Ensure Nginx or Apache permanent redirects are active and mapped correctly.
  3. Execute Change of Address: Inside the GSC dashboard for the old domain, navigate to Settings > Change of Address and run the verification checks.
  4. Submit Sitemaps: Submit your new XML sitemaps to GSC immediately after the migration is verified.

#7. Migration Checklist

Use this checklist to manage your WordPress domain migrations:

  • Run a full SQL and filesystem backup of the old site.
  • Configure the new domain host server and install SSL certificates.
  • Import the SQL database into the new database server.
  • Execute WP-CLI search-replace dry run and verify statistics.
  • Run the final replacement commands for HTTP and HTTPS variations.
  • Flush rewrite rules, clear object caches, and verify links.
  • Set up web server 301 redirect rules (Nginx/Apache).
  • Submit the Change of Address notification inside Google Search Console.

#8. Action Plan: A 90-Day Domain Migration Strategy

Follow this timeline to plan and monitor your site’s domain migration:

  • Days 1–30 (Staging & Testing): Audit your existing hosting environment and verify that the target destination server supports SSL/HTTPS configurations. Execute database dry-run simulations using WP-CLI to map all planned URL replacements, and configure local staging domains with dynamic overrides in wp-config.php to run layout checks.
  • Days 31–60 (Database Updates & Server Redirects): Export a pre-migration database backup, import the schema into your target database server, and execute actual serialization-aware search-and-replace queries. Set up web server wildcard 301 permanent redirect blocks using Nginx or Apache config parameters, and configure Cloudflare workers for edge-level redirects.
  • Days 61–90 (SEO Audits & Crawl Monitoring): Verify both properties in Google Search Console and submit the Change of Address notification. Upload new XML sitemaps to verify indexing status, inspect web logs to check search bot crawl budgets, and run broken-link diagnostic sweeps to resolve redirection issues.

Need help migrating your WordPress database or configuring server-level redirect rules? Our WordPress development team can audit your database, execute secure migrations, and manage search console setups. Contact us to discuss your project requirements.

Next step

Turn the article into an actual implementation

This block strengthens internal linking and gives readers the most relevant next move instead of leaving them at a dead end.

Want this implemented on your site?

If visibility in Google and AI systems matters, I can build the content architecture, FAQ, schema, and internal linking needed for SEO, GEO, and AEO.

Related cluster

Explore other WordPress services and knowledge base

Strengthen your business with professional technical support in key areas of the WordPress ecosystem.

Why does manual SQL replace break WordPress layouts?#
Because it modifies string values without updating the associated string-length markers (e.g. s:20) in serialized arrays, causing deserialization failures.
How can I change the domain using WP-CLI safely?#
First run a simulation using wp search-replace --dry-run. If correct, execute the command without the dry-run flag.
Should I leave WP_HOME overrides in wp-config.php permanently?#
No. Use them as temporary staging overrides. Hardcoding values in configuration files blocks admin-side URL updates.
How do I notify search engines of a domain change?#
Map 301 permanent redirects from the old domain to the new domain, verify both properties in Google Search Console, and run the Change of Address tool.
What is the difference between home and siteurl options in WordPress?#
The home option (WP_HOME) defines the address visitors use to access the site. The siteurl option (WP_SITEURL) defines the folder where the core application files reside. If you place WordPress files in a subdirectory, these values will differ.

Need an FAQ tailored to your industry and market? We can build one aligned with your business goals.

Let’s discuss

Related Articles