WordPress migration guide: move your site safely

WordPress migration guide: move your site safely

Last verified: October 13, 2026
25 min read
Guide
Full-stack developer
500+ WP projects

Moving your WordPress website can be a daunting task, but with the right knowledge and preparation, it becomes a manageable process. Whether you’re changing domains, upgrading hosting, or restructuring your site architecture, this comprehensive guide will walk you through every step of the WordPress migration process.

#WordPress address vs site address URL

Before diving into migration techniques, it’s crucial to understand the two fundamental URL settings in WordPress:

  • WordPress Address (URL): This is where your WordPress core files reside
  • Site Address (URL): This is the address visitors type in their browser to reach your site

Both settings should include the https:// part and should not have a trailing slash / at the end. These settings control how WordPress displays URLs throughout your site, including the admin section and frontend.

#Why WordPress migration becomes necessary

Several scenarios might require you to migrate your WordPress site:

  1. Domain Changes: Rebranding or switching to a better domain name
  2. Hosting Migration: Moving to a better hosting provider
  3. Server Changes: Upgrading server infrastructure
  4. Site Restructuring: Moving WordPress to a subdirectory or root directory
  5. Development to Production: Moving from staging to live environment
  6. HTTP to HTTPS: Implementing SSL certificates

#How to back up WordPress before migration

#What to back up before a WordPress migration

Before attempting any migration, create comprehensive backups:

  1. Database Backup: Export your WordPress database via phpMyAdmin or WP-CLI
  2. File Backup: Download all WordPress files and directories
  3. Configuration Backup: Save your wp-config.php file separately
  4. Plugin/Theme Settings: Document custom configurations

#How to test a migration on a staging copy

Always test migrations in a staging environment before going live:

## Create a test subdirectory
mkdir /var/www/html/test-site
## Copy files to test location
cp -r /var/www/html/wordpress/* /var/www/html/test-site/

#How to change WordPress URLs after migration

#Method 1: set WP_HOME and WP_SITEURL in wp-config.php

For temporary URL changes, add these lines to your wp-config.php:

define('WP_HOME', 'https://example.com');
define('WP_SITEURL', 'https://example.com');

Pros: Quick and immediate Cons: Hard-coded values, can’t edit in WordPress admin anymore

#Method 2: update siteurl and home in functions.php

If you have FTP access but can’t access WordPress admin:

  1. Access your active theme’s functions.php file
  2. Add these lines after the opening <?php tag:
update_option('siteurl', 'https://example.com');
update_option('home', 'https://example.com');
  1. Upload the file and visit your admin panel
  2. Important: Remove these lines after the site is working

#Method 3: the RELOCATE constant in wp-config.php

WordPress includes an automatic relocation method:

  1. Edit wp-config.php and add before “That’s all, stop editing!”:
define('RELOCATE', true);
  1. Visit https://yoursite.com/wp-login.php
  2. Login normally
  3. Check Settings > General to verify URLs
  4. Remove the RELOCATE constant afterward

Security Warning: Never leave RELOCATE constant in wp-config.php as it creates security vulnerabilities.

#Method 4: edit siteurl and home in phpMyAdmin

For precise control, edit URLs directly in the database:

  1. Access phpMyAdmin
  2. Select your WordPress database
  3. Find wp_options table (prefix may vary)
  4. Edit the ‘siteurl’ and ‘home’ rows
  5. Update option_value to new URLs

Critical: Always backup your database before making direct edits!

#Manual WordPress migration to a new server or domain

#How to move WordPress to a new server

When migrating to a new server:

  1. Backup Everything: Complete site and database backup
  2. Export Database: Use phpMyAdmin or WP-CLI
  3. Transfer Files: Use FTP, SFTP, or rsync
  4. Import Database: Create new database and import
  5. Update wp-config.php: Modify database credentials
  6. Update URLs: Use one of the methods above
  7. Test Thoroughly: Check all functionality

#How to change the WordPress domain with WP-CLI

Changing domains requires special attention to serialized data:

## Using WP-CLI (recommended)
wp search-replace 'olddomain.com' 'newdomain.com' --skip-columns=guid

## Or use specialized plugins like:
## - Better Search Replace

Important: Never update the GUID column in wp_posts table. GUID stands for Globally Unique Identifier and should never change to maintain feed reader compatibility.

#Subdirectory to root migration

Moving WordPress from a subdirectory to root:

  1. Update URLs in WordPress Admin: Settings > General
  2. Copy Files: Move WordPress files to new location
  3. Update .htaccess: Modify rewrite rules
  4. Update Permalinks: Resave permalink structure
  5. Check Internal Links: Update hardcoded URLs

#Multisite migration

WordPress Multisite requires additional considerations:

  1. Backup Network: All sites and databases
  2. Edit wp-config.php: Update multisite constants
  3. Update .htaccess: Modify multisite rewrite rules
  4. Database Updates: Update wp_blogs and wp_site tables
  5. Individual Site Options: Update each site’s options tables

#WordPress post-migration checklist

#What to test right after the migration

  • Test frontend functionality
  • Verify admin access
  • Check all forms and submissions
  • Test e-commerce functionality
  • Verify user login/registration

#301 redirects and sitemap after a domain change

  • Implement 301 redirects from old URLs
  • Update sitemap.xml
  • Submit new sitemap to search engines
  • Update Google Analytics property
  • Verify Google Search Console

#Cache, database and SSL checks after migration

  • Clear all caches
  • Optimize database tables
  • Check plugin compatibility
  • Test site speed
  • Verify SSL certificate

#WordPress migration errors and how to fix them

#White screen of death after migration

Usually caused by:

  • Memory limit exhaustion
  • Plugin conflicts
  • Theme incompatibility

Solution: Increase memory limit in wp-config.php:

define('WP_MEMORY_LIMIT', '256M');

#Error establishing a database connection after migration

Check wp-config.php settings:

define('DB_NAME', 'database_name');
define('DB_USER', 'username');
define('DB_PASSWORD', 'password');
define('DB_HOST', 'localhost');

#Mixed content warnings after moving to HTTPS

HTTP resources on HTTPS pages cause security warnings:

## Find mixed content
grep -r "http://" wp-content/

Update media URLs in database:

UPDATE wp_posts SET post_content = REPLACE(post_content,'olddomain.com/wp-content/uploads','newdomain.com/wp-content/uploads');

#WordPress migration plugins and WP-CLI tools

#Best WordPress migration plugins

  1. All-in-One WP Migration: Complete site migration tool
  2. Duplicator: Create migration packages easily
  3. WP Migrate Lite (formerly WP Migrate DB): database export with find and replace
  4. Better Search Replace: search and replace in the dashboard, with serialized data support (Velvet Blues Update URLs was closed in the plugin directory on 3 May 2024)
  5. Migrator: single-file backup and migration with no artificial size cap (All-in-One WP Migration’s own FAQ says the import limit comes from the host’s upload size, not from the plugin) and serialization-safe URL rewriting. See the backup and migration guide.

#WP-CLI commands for export, import and search-replace

## How to export a WordPress database with WP-CLI
wp db export backup.sql

## WP-CLI database import
wp db import backup.sql

## Search and replace
wp search-replace 'old-url' 'new-url' --dry-run

#WordPress migration security checklist

  1. Use HTTPS: Ensure SSL is configured on new server
  2. Update File Permissions: Secure wp-config.php (600) and directories (755)
  3. Remove Migration Scripts: Delete temporary files and constants
  4. Update Security Keys: Generate new WordPress keys in wp-config.php
  5. Monitor Logs: Watch for unusual activity post-migration

#How to speed up WordPress after migration

#How to optimize database tables with OPTIMIZE TABLE

OPTIMIZE TABLE wp_posts;
OPTIMIZE TABLE wp_postmeta;
OPTIMIZE TABLE wp_options;

#Caching and CDN setup on the new host

  • Configure page caching
  • Set up browser caching
  • Enable CDN integration
  • Optimize database caching

#How to test a WordPress site after migration

#Testing menus, forms, search and media

  1. Navigation: All menu items work correctly
  2. Forms: Contact forms and submissions function
  3. Search: Site search returns results
  4. Comments: Comment system works
  5. Media: Images and videos load properly

#Checking meta tags, canonical URLs and schema

  1. Meta Tags: Titles and descriptions display correctly
  2. Canonical URLs: Point to new domain
  3. Structured Data: Schema markup validates
  4. Internal Links: All links work and redirect properly

#Monitoring WordPress after a migration

#Uptime, Search Console and 404 monitoring

  • Set up uptime monitoring
  • Monitor Google Search Console for errors
  • Track analytics for traffic patterns
  • Watch for 404 errors in logs

#Ongoing maintenance after the move

  • Regular database optimization
  • Image optimization and compression
  • Plugin performance monitoring
  • Security scan implementation

#How to move WordPress without downtime using DNS TTL

A resolver may cache a DNS record for as long as its TTL allows (RFC 1035, section 3.2.1). That fixes the order of the steps:

  • Lower the TTL in advance. Set the A and AAAA records to a short TTL, for example 300 seconds, at least one full old TTL before the switch. If the old value was 86400 seconds, that means a day ahead, because until it expires resolvers keep answering with the old IP.
  • Test the new server through the hosts file. Map the domain to the new IP in your local hosts file and go through checkout, login and forms on the target server before the public record changes. Visitors still reach the old server.
  • Freeze writes for the final dump. Orders, comments and form entries that reach the old server after the last wp db export do not travel. Put the old site into maintenance mode, or stop publishing, for the final export and import.

#How to transfer a large WordPress media library with rsync

FTP copies every file again on every attempt. rsync over SSH compares source and destination and sends only the differences, so a second run moves only what changed since the first one (rsync manual).

  • Run it twice. The first pass copies wp-content/uploads while the old site is still live. The second pass, during the write freeze, picks up the files added in the meantime.
  • Replace URLs with WP-CLI, not with SQL. WordPress stores options and widget settings as serialized PHP arrays that record string lengths. wp search-replace rewrites those lengths, a plain SQL REPLACE() does not.
  • Regenerate thumbnails if the theme changed. wp media regenerate rebuilds thumbnails for the image sizes registered on the new install, and --only-missing limits it to images that lack some sizes (WP-CLI: wp media regenerate).

#How to check SEO, SSL and Core Web Vitals after a WordPress migration

  • Crawl the old URL list. Every old URL should answer with a 301 to its new counterpart, and no internal link should return a 404. Google recommends server-side permanent redirects from old to new URLs for a site move, and a Change of Address in Search Console when the domain changes (Google Search Central: site moves with URL changes).
  • TLS and HSTS. Install the certificate on the new server before the switch. HTTP Strict Transport Security makes browsers use HTTPS only for the domain for the whole max-age period (RFC 6797), so turn it on only once HTTPS works on every subdomain it covers.
  • Measure before and after. Run PageSpeed Insights on the same templates on the old and the new server. Page cache, OPcache, object cache and gzip or Brotli compression on the new host show up in the server response time.

#GDPR obligations when changing WordPress hosting

For a site run by a company in the EU, a new host is also a new processor of personal data:

  • A data processing agreement with the new host. Article 28 GDPR requires a contract that sets out the subject matter, duration, nature and purpose of the processing, and binds the processor to the security measures of Article 32 (GDPR, Regulation (EU) 2016/679).
  • Update the privacy policy. If the move adds a CDN, a new backup location or a new host, the list of recipients in the privacy policy changes with it.
  • Deletion at the old host. Under Article 28(3)(g) the processor deletes or returns the personal data at the end of the service, at the controller’s choice. Once the new server runs stable, ask the old host to confirm that files, databases and backups are deleted.

#WordPress deployment with GitHub Actions and rollback

  • Deploy from the repository. A GitHub Actions or GitLab CI workflow builds theme assets and ships code on every push to the main branch, so the server never carries hand-edited files.
  • Keep the previous release. Deploy each release into its own directory and switch a symlink. Rolling back code means pointing the symlink at the previous release. Take a wp db export before any step that changes the database, because a code rollback does not undo changes to tables or content.
  • The same versions everywhere. Match the PHP and MySQL or MariaDB versions between local, staging and production, so a migration tested on staging behaves the same way in production.

#How wp search-replace handles serialized data

The WordPress handbook warns that a blanket search and replace over the database can break serialized data, and that the guid column in wp_posts must never be changed (Moving WordPress). wp search-replace is built for this case: it handles PHP serialized data and does not change primary key values (WP-CLI: wp search-replace). The flags that matter during a migration:

  • --dry-run reports what would change and saves nothing.
  • --skip-columns=guid keeps post GUIDs as they are.
  • --precise forces PHP processing for every column. It is slower and more reliable for complex serialized data.
  • --network covers all tables of a multisite network. Without it, only the current site’s tables are searched.
  • --all-tables-with-prefix adds tables that plugins created with the WordPress prefix without registering them, and --all-tables takes every table in the database.
  • --export=file.sql writes the changed data to an SQL file instead of the database, which helps when the target database sits on another server.

Tables without a primary key are skipped, so a plugin table without one needs a separate check. On multisite the handbook also lists what to review by hand: the wp_blogs and wp_site tables and the home, siteurl and fileupload_url options of every site.

wp db export and wp db import round this off. Export runs mysqldump with the credentials from wp-config.php and accepts any mysqldump flag, and wp db export - writes to STDOUT (WP-CLI: wp db export). Import does not create the database, it only runs the SQL from the file, and wp db import - reads from STDIN (WP-CLI: wp db import). Create the empty database on the target first.

#WordPress migration case studies

#Case study 1: moving a 50,000-product store to a new domain

Scenario: A popular online store with 50,000+ products needed to migrate from store-old.com to brandnew.com while maintaining SEO rankings and customer trust.

Challenges Faced:

  • Massive product database with complex variations
  • Active shopping cart sessions during migration
  • Third-party payment gateway integrations
  • Customer email campaigns with old domain links

Migration Strategy:

  1. Pre-migration preparation (2 weeks):

    • Created complete site backup including customer data
    • Set up temporary staging environment
    • Tested all payment gateway integrations
    • Prepared email templates for customer notification
  2. Technical implementation (4 hours):

    • Used WP-CLI for database URL replacement
    • Implemented custom 301 redirect rules
    • Updated all third-party API endpoints
    • Configured SSL certificate for new domain
  3. Post-migration optimization (1 week):

    • Monitored Google Search Console for indexing issues
    • Updated all marketing automation workflows
    • Implemented enhanced tracking for migration impact
    • Conducted customer satisfaction survey

Results:

  • Nearly all SEO rankings maintained within 2 weeks
  • Zero data loss during migration
  • Customer complaints fell sharply with proper communication
  • Site speed improved on the new hosting infrastructure

Key Lessons Learned:

  • Customer communication is as important as technical execution
  • Testing payment gateways in staging environment prevents revenue loss
  • Having rollback plan ready provides confidence during execution

#Case study 2: moving a 200-site multisite network to the cloud

Scenario: A educational institution with 200+ subdomain sites needed to migrate from shared hosting to dedicated cloud infrastructure.

Technical Complexity:

  • 201 individual WordPress sites
  • Custom user roles and permissions across sites
  • Shared media library with 100GB+ of content
  • Complex plugin dependencies between sites

Migration Approach:

  1. Network Analysis Phase:

    • Mapped all site interdependencies
    • Identified custom plugin configurations
    • Documented user role hierarchies
    • Analyzed media library usage patterns
  2. Staging Environment Setup:

    • Replicated exact server configuration
    • Created automated testing scripts
    • Implemented performance monitoring
    • Set up rollback procedures
  3. Phased Migration:

    • Migrated 10 pilot sites first
    • Documented and refined process
    • Batch migrated remaining sites
    • Continuous monitoring and optimization

Technical Solutions:

## Custom script for batch multisite migration
#!/bin/bash
for site in $(wp site list --field=url); do
    echo "Migrating $site"
    wp search-replace 'old-domain.com' 'new-domain.com' --url=$site
    wp cache flush --url=$site
done

Outcomes:

  • 99.8% uptime during migration process
  • 40% improvement in page load times
  • Zero data corruption across all sites
  • Simplified administration through centralized management

#Case study 3: moving a blog to HTTPS and fixing mixed content

Scenario: A content-heavy blog with 10,000+ posts and extensive media library needed HTTPS implementation.

Mixed Content Challenges:

  • Hardcoded HTTP links in post content
  • External resources without HTTPS alternatives
  • Embedded iframes from non-secure sources
  • Custom CSS with HTTP references

Full pipeline:

  1. Content Audit and Cleanup:
-- Find and replace HTTP references in posts
UPDATE wp_posts 
SET post_content = REPLACE(post_content, 'http://example.com', 'https://example.com')
WHERE post_content LIKE '%http://example.com%';

-- Leave the guid column alone: the WordPress docs say never change it
  1. Plugin Configuration Updates:
// Custom functions.php additions for HTTPS enforcement
add_action('template_redirect', function() {
    if (!is_ssl() && !is_admin()) {
        wp_redirect('https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], 301);
        exit;
    }
});
  1. Advanced .htaccess Configuration:
## How to force HTTPS for all WordPress traffic
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Performance Impact:

  • Initial 5% traffic dip during first week
  • Full recovery within 3 weeks
  • 10% improvement in search rankings
  • Enhanced user trust and security metrics

#Migrating large WordPress databases and serialized data

#Database migration optimization strategies

#Handling large databases efficiently

For databases exceeding 1GB, standard export/import methods often fail. Here are advanced techniques:

Method 1: Chunked Export with WP-CLI

## Export in chunks for large databases
wp db export chunk1.sql --tables=wp_posts,wp_postmeta --add-drop-table
wp db export chunk2.sql --tables=wp_options,wp_users,wp_usermeta --add-drop-table

## Combine and import separately
mysql new_database < chunk1.sql
mysql new_database < chunk2.sql

Method 2: stream the dump through gzip instead of a custom PHP script

## Stream the dump straight into gzip, no uncompressed file on disk
wp db export - --single-transaction --quick | gzip > db.sql.gz

## Import from STDIN on the target
gunzip < db.sql.gz | wp db import -

#Serialized data handling

WordPress stores many settings as serialized PHP data, which breaks when URL lengths change:

Serialization-safe replacement with WP-CLI:

## Preview first, then run without --dry-run
wp search-replace 'https://olddomain.com' 'https://newdomain.com' --skip-columns=guid --precise --dry-run
wp search-replace 'https://olddomain.com' 'https://newdomain.com' --skip-columns=guid --precise

#Bash script to automate a WordPress migration

#Automated migration framework

Create a comprehensive migration automation script:

#!/bin/bash
## WordPress Migration Automation Script

set -e  # Exit on any error

## Configuration
OLD_DOMAIN="old-site.com"
NEW_DOMAIN="new-site.com"
BACKUP_DIR="/path/to/backups"
STAGING_DIR="/path/to/staging"
LOG_FILE="$BACKUP_DIR/migration_$(date +%Y%m%d_%H%M%S).log"

## Logging function
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

## Pre-migration checks
pre_migration_checks() {
    log "Starting pre-migration checks"
    
    # Check WordPress installation
    if ! wp core is-installed --path=/var/www/html; then
        log "ERROR: WordPress installation not found"
        exit 1
    fi
    
    # Check database connection
    if ! wp db check --path=/var/www/html; then
        log "ERROR: Database connection failed"
        exit 1
    fi
    
    # Create backup directory
    mkdir -p "$BACKUP_DIR"
    mkdir -p "$STAGING_DIR"
    
    log "Pre-migration checks completed"
}

## Backup creation
create_backups() {
    log "Creating comprehensive backups"
    
    # Database backup
    wp db export "$BACKUP_DIR/database_$(date +%Y%m%d_%H%M%S).sql" --path=/var/www/html
    
    # Files backup
    tar -czf "$BACKUP_DIR/files_$(date +%Y%m%d_%H%M%S).tar.gz" /var/www/html
    
    # Configuration backup
    cp /var/www/html/wp-config.php "$BACKUP_DIR/wp-config_$(date +%Y%m%d_%H%M%S).php"
    
    log "Backups completed successfully"
}

## URL replacement
replace_urls() {
    log "Starting URL replacement process"
    
    # Core WordPress URLs
    wp search-replace "$OLD_DOMAIN" "$NEW_DOMAIN" --skip-columns=guid --path=/var/www/html
    
    log "URL replacement completed"
}

## Post-migration validation
post_migration_validation() {
    log "Starting post-migration validation"
    
    # Test site accessibility
    if curl -f -s "https://$NEW_DOMAIN" > /dev/null; then
        log "Site is accessible"
    else
        log "ERROR: Site is not accessible"
        exit 1
    fi
    
    # Test WordPress admin
    if curl -f -s "https://$NEW_DOMAIN/wp-admin/" > /dev/null; then
        log "WordPress admin is accessible"
    else
        log "ERROR: WordPress admin is not accessible"
        exit 1
    fi
    
    # Check database integrity
    wp db check --path=/var/www/html
    
    log "Post-migration validation completed"
}

## Main execution
main() {
    log "Starting WordPress migration process"
    
    pre_migration_checks
    create_backups
    replace_urls
    post_migration_validation
    
    log "Migration completed successfully"
}

## Execute main function
main

#Nginx, PHP and MySQL settings for a migrated site

#Nginx and PHP settings after migration

#Nginx configuration for migrated sites

server {
    listen 80;
    server_name new-site.com www.new-site.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name new-site.com www.new-site.com;
    
    root /var/www/html;
    index index.php index.html;
    
    # SSL Configuration
    ssl_certificate /path/to/ssl/cert.pem;
    ssl_certificate_key /path/to/ssl/private.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
    
    # WordPress-specific configuration
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
    
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        
        # Performance optimizations
        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_busy_buffers_size 256k;
    }
    
    # Caching configuration
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
}

#PHP optimization for migration performance

; PHP configuration optimized for WordPress migration
memory_limit = 512M
max_execution_time = 300
max_input_time = 300
upload_max_filesize = 64M
post_max_size = 64M
max_input_vars = 3000

; OPcache configuration for better performance
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=2
opcache.fast_shutdown=1

#Database performance tuning

#MySQL configuration optimization

-- MySQL optimization for WordPress
SET GLOBAL innodb_buffer_pool_size = 1073741824; -- 1GB
SET GLOBAL innodb_log_file_size = 268435456; -- 256MB
SET GLOBAL innodb_flush_log_at_trx_commit = 2;
SET GLOBAL innodb_flush_method = O_DIRECT;

-- Query cache configuration
SET GLOBAL query_cache_type = ON;
SET GLOBAL query_cache_size = 67108864; -- 64MB

#Database indexing strategy

-- Add indexes for better performance
ALTER TABLE wp_posts ADD INDEX idx_post_type_status (post_type, post_status);
ALTER TABLE wp_postmeta ADD INDEX idx_meta_key_value (meta_key, meta_value(191));
ALTER TABLE wp_options ADD INDEX idx_autoload (autoload, option_name);

-- Optimize large tables
OPTIMIZE TABLE wp_posts;
OPTIMIZE TABLE wp_postmeta;
OPTIMIZE TABLE wp_options;

#Fix serialized data and memory errors after migration

#Serialized data, memory and plugin errors

#Issue 1: Serialized data corruption

Problem: After URL replacement, theme options and plugin settings become corrupted.

Diagnosis:

<?php
// List options whose serialized value no longer unserializes (run with wp eval-file)
global $wpdb;
$rows = $wpdb->get_results( "SELECT option_name, option_value FROM {$wpdb->options}" );
foreach ( $rows as $row ) {
    $v = $row->option_value;
    if ( is_serialized( $v ) && 'b:0;' !== $v && false === @unserialize( $v ) ) {
        echo $row->option_name . "\n";
    }
}

Solution:

## Restore the pre-replace dump and repeat the replacement with WP-CLI
wp db import backup-before-replace.sql
wp search-replace 'https://olddomain.com' 'https://newdomain.com' --skip-columns=guid --precise

#Issue 2: Memory exhaustion during large database migration

Problem: PHP memory limits exceeded during database import/export.

Solutions:

Method 1: Chunked Processing

<?php
// Process database in chunks to avoid memory issues
function process_database_in_chunks($table, $chunk_size = 1000) {
    global $wpdb;
    
    $offset = 0;
    do {
        $results = $wpdb->get_results("
            SELECT * FROM $table 
            LIMIT $offset, $chunk_size
        ");
        
        foreach ($results as $row) {
            // Process each row
            process_row($row);
        }
        
        $offset += $chunk_size;
        
        // Clear memory
        $wpdb->flush();
        
    } while (count($results) === $chunk_size);
}
?>

Method 2: Streaming Database Export

## Use mysqldump with compression for large databases
mysqldump --single-transaction --quick --lock-tables=false \
  --host=localhost --user=username --password=password \
  database_name | gzip > backup.sql.gz

## Import with streaming
gunzip < backup.sql.gz | mysql -u username -p database_name

#Issue 3: Plugin compatibility after migration

Problem: Plugins fail to work after URL changes due to hardcoded references.

Systematic Solution:

  1. Create Plugin Compatibility Checker:
<?php
// Plugin compatibility checker
function check_plugin_compatibility() {
    $plugins = get_option('active_plugins');
    $issues = [];
    
    foreach ($plugins as $plugin_path) {
        $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_path);
        
        // Check for hardcoded URLs in plugin files
        $plugin_files = glob(WP_PLUGIN_DIR . '/' . dirname($plugin_path) . '/*.php');
        
        foreach ($plugin_files as $file) {
            $content = file_get_contents($file);
            
            // Look for potential hardcoded URLs
            if (preg_match('/https?:\/\/[^\s"\'<]+/', $content, $matches)) {
                $issues[] = [
                    'plugin' => $plugin_data['Name'],
                    'file' => $file,
                    'hardcoded_urls' => $matches
                ];
            }
        }
    }
    
    return $issues;
}
?>
  1. Replace URLs in plugin settings with WP-CLI:
## wp_options only, PHP mode for nested serialized values
wp search-replace 'https://olddomain.com' 'https://newdomain.com' wp_options --precise --dry-run

#Automate WordPress migrations with GitHub Actions

#How to test migrations in a CI pipeline

#GitHub Actions workflow for automated testing

name: WordPress Migration Test

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  migration-test:
    runs-on: ubuntu-latest
    
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: wordpress_test
        options: >-
          --health-cmd="mysqladmin ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '8.1'
        extensions: mysqli, gd, xml, mbstring
    
    - name: Install WordPress
      run: |
        curl -O https://wordpress.org/latest.tar.gz
        tar -xzf latest.tar.gz
        cp -r wordpress/* /var/www/html/
        
    - name: Configure WordPress
      run: |
        cp wp-config-sample.php wp-config.php
        sed -i "s/database_name_here/wordpress_test/" wp-config.php
        sed -i "s/username_here/root/" wp-config.php
        sed -i "s/password_here/password/" wp-config.php
        sed -i "s/localhost/127.0.0.1/" wp-config.php
        
    - name: Install WP-CLI
      run: |
        curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
        chmod +x wp-cli.phar
        sudo mv wp-cli.phar /usr/local/bin/wp
        
    - name: Run Migration Tests
      run: |
        wp core install --url=http://localhost --title="Test Site" --admin_user=admin --admin_password=password [email protected]
        wp plugin install all-in-one-wp-migration --activate
        # Add your migration testing scripts here
        
    - name: Validate Migration
      run: |
        # Add validation scripts
        wp option get siteurl
        wp option get home

#Automated deployment script

#!/bin/bash
## Automated WordPress Deployment Script

set -e

## Environment variables
ENVIRONMENT=${1:-staging}
BACKUP_DIR="/backups/wordpress"
DEPLOY_LOG="/var/log/deploy.log"

## Logging function
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$ENVIRONMENT] $1" | tee -a "$DEPLOY_LOG"
}

## Pre-deployment checks
pre_deployment_checks() {
    log "Starting pre-deployment checks"
    
    # Check if WordPress is installed
    if ! wp core is-installed --path=/var/www/html; then
        log "ERROR: WordPress not properly installed"
        exit 1
    fi
    
    # Check database connectivity
    if ! wp db check --path=/var/www/html; then
        log "ERROR: Database connection failed"
        exit 1
    fi
    
    # Verify required plugins are active
    if ! wp plugin is-active all-in-one-wp-migration --path=/var/www/html; then
        log "ERROR: Required migration plugin not active"
        exit 1
    fi
    
    log "Pre-deployment checks passed"
}

## Create deployment backup
create_deployment_backup() {
    log "Creating deployment backup"
    
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local backup_file="$BACKUP_DIR/pre_deploy_${ENVIRONMENT}_$timestamp.sql"
    
    wp db export "$backup_file" --path=/var/www/html
    
    # Verify backup integrity
    if [ -s "$backup_file" ] && tail -n 1 "$backup_file" | grep -q "Dump completed"; then
        log "Backup created and verified: $backup_file"
    else
        log "ERROR: Backup creation failed"
        exit 1
    fi
}

## Execute migration
execute_migration() {
    log "Executing WordPress migration"
    
    # Update site URLs if needed
    if [ "$ENVIRONMENT" = "production" ]; then
        wp search-replace "staging.example.com" "example.com" --skip-columns=guid --path=/var/www/html
    fi
    
    # Flush all caches
    wp cache flush --path=/var/www/html
    
    # Update rewrite rules
    wp rewrite flush --path=/var/www/html
    
    log "Migration completed"
}

## Post-deployment validation
post_deployment_validation() {
    log "Starting post-deployment validation"
    
    # Check site accessibility
    local site_url=$(wp option get siteurl --path=/var/www/html)
    
    if curl -f -s "$site_url" > /dev/null; then
        log "Site is accessible: $site_url"
    else
        log "ERROR: Site is not accessible"
        exit 1
    fi
    
    # Check WordPress admin
    if curl -f -s "$site_url/wp-admin/" > /dev/null; then
        log "WordPress admin is accessible"
    else
        log "ERROR: WordPress admin is not accessible"
        exit 1
    fi
    
    # Verify database integrity
    if wp db check --path=/var/www/html; then
        log "Database integrity verified"
    else
        log "ERROR: Database integrity check failed"
        exit 1
    fi
    
    # Check critical plugins
    local critical_plugins=("all-in-one-wp-migration" "wp-super-cache")
    
    for plugin in "${critical_plugins[@]}"; do
        if wp plugin is-active "$plugin" --path=/var/www/html; then
            log "Plugin $plugin is active"
        else
            log "WARNING: Plugin $plugin is not active"
        fi
    done
    
    log "Post-deployment validation completed"
}

## Main deployment function
main() {
    log "Starting WordPress deployment for $ENVIRONMENT environment"
    
    pre_deployment_checks
    create_deployment_backup
    execute_migration
    post_deployment_validation
    
    log "Deployment completed successfully"
    
    # Send notification (optional)
    if command -v slack-cli &> /dev/null; then
        slack-cli send "WordPress deployment to $ENVIRONMENT completed successfully"
    fi
}

## Execute main function
main

#Conclusion

WordPress migration doesn’t have to be overwhelming when approached systematically. By following this comprehensive guide, preparing thoroughly, and testing diligently, you can successfully migrate your WordPress site with minimal disruption to your users and SEO performance.

Remember that preparation is key, never attempt a migration without complete backups and a clear rollback plan. Take your time, test thoroughly, and don’t hesitate to seek professional help for complex migrations.

With proper planning and execution, your WordPress migration can be a smooth process that sets your site up for future growth and success.

For the delivery side of this topic we keep the work under Next.js or Astro migration.

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.

How long does a WordPress migration take?#
Migration time varies by site size: Small sites (1-5GB): 1-2 hours, Medium sites (5-20GB): 2-4 hours, Large sites (20GB+): 4+ hours.
Do I need technical skills for WordPress migration?#
Basic technical skills are helpful, but with proper guidance and tools, even beginners can successfully migrate WordPress sites.
Will my SEO be affected by migration?#
Properly executed migration with 301 redirects preserves SEO rankings. Always inform Google about domain changes through Search Console.
What's the difference between manual and plugin migration?#
Manual migration offers full control but requires technical knowledge. Plugin migration is easier but may have limitations for complex sites.
How do I migrate a multisite WordPress installation?#
Multisite migration requires special handling of network configurations, shared databases, and domain mapping. Consider professional help for complex multisite migrations.

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

Let’s discuss

Related Articles