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:
- 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.
- 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.