Migration Memo: The Ultimate Pitfall Guide & Best Practices

Overview

This guide documents the high-stakes migration of a multi-service web ecosystem (WordPress, ComfyUI, Dify, GPT, etc.) from Server A to Server B. Our goal was to achieve a seamless transition of dozens of subdomains while maintaining SSL integrity and remote database connectivity.

Phase 1: The “Connectivity” Layer & Corporate Firewalls

Before moving data, we addressed the “SMB Trap.”

  • The Issue: Port 445 (SMB) was blocked by corporate DPI (Deep Packet Inspection) for international traffic.
  • The Lesson: Never rely on raw sensitive protocols over public IPs.
  • Resolution: Encapsulate traffic using Tailscale or SSH Tunneling to bypass inspection.

Phase 2: File Transfer & Directory Traps

During the move, two common Linux file-handling errors occurred:

  1. The SCP “Fake File” Trap: If the target directory doesn’t exist, SCP saves the upload as a file with that name instead of a folder.
  2. The Tar “Nested Doll” Effect: Extracting a package into a pre-made folder often creates redundant layers (e.g., /data/site/site/).

The Recovery Fix:

# Rename the "fake" file back to archive
mv /data /data_backup.tar.gz

# Create correct target
mkdir -p /data/ruianding.com

# Flatten
tar -xzvf /data_backup.tar.gz -C /data/ruianding.com --strip-components=1 

Phase 3: Apache Mass Migration

With over 10 subdomains, manual configuration is a recipe for disaster. We used Environment Mirroring.

1. Packaging Everything (Server A)

Follow symbolic links with -h to ensure SSL certificates are actually included, not just the links.

cd /etc/apache2
sudo tar -czvf sites_backup.tar.gz sites-available/
sudo tar -chzvf letsencrypt_all.tar.gz /etc/letsencrypt/

2. Environment Mirroring (Server B)

# Install environment
sudo apt update && sudo apt install apache2 certbot php-mysql -y

# Enable Proxy & SSL modules
sudo a2enmod proxy proxy_http proxy_balancer lbmethod_byrequests ssl rewrite headers

# Batch enable sites
cd /etc/apache2/sites-available
ls *.conf | xargs -i sudo a2ensite {}

sudo systemctl restart apache2

Phase 4: The MySQL 8.0 & Latency “Reality Check”

1. The Syntax Barrier (MySQL 8.0+)

Pitfall: GRANT … IDENTIFIED BY is deprecated and throws Error 1064.

Lesson: Identity and Authorization are now separate.

-- On Server A

CREATE USER 'user_id'@'Server_B_IP' IDENTIFIED BY 'your_actual_password';
GRANT ALL PRIVILEGES ON database.* TO 'user_id'@'Server_B_IP';
FLUSH PRIVILEGES;

2. The Hostfile Hack

To decouple code from infrastructure, we mapped the DB host at the OS level on Server B:

# /etc/hosts
[Server_A_IP]  mysql_remote_host

# wp-config.php
define( 'DB_HOST', 'mysql_remote_host' );

3. The “Latency Tax”

We initially attempted a distributed setup: the Database remained on Server A (Japan), while the WordPress Code lived on Server B (China).

The Observation: The website became extremely sluggish, often taking 5–10 seconds just to render a basic page.

The “Chatty” Reality: WordPress is a “chatty” application. To load a single page, it doesn’t just ask the database once; it sends dozens of small queries to fetch options, metadata, and content.

The Math:

  • Single Query Latency (RTT): ~60ms (China to Japan)
  • WordPress Page Load: ~80 SQL queries
  • Total Wait Time: 80 * 60 ms = 4,800 ms = 4.8 seconds of pure silence while the server waits for data to cross the ocean.

The Verdict: Database proximity is non-negotiable for monolithic apps like WordPress. In a cross-border setup, the “Latency Tax” is too high to pay. Moving the database to the same local network as the code is the only way to achieve sub-second load times.