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:
mv /data /data_backup.tar.gz # Rename the "fake" file back to archive mkdir -p /data/ruianding.com # Create correct target tar -xzvf /data_backup.tar.gz -C /data/ruianding.com --strip-components=1 # Flatten
Phase 3: Apache Mass Migration (The Scalability Lesson)
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.
Bash
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)
Bash
# 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.
SQL
-- On Server A CREATE USER 'ruiand'@'Server_B_IP' IDENTIFIED BY 'your_actual_password'; GRANT ALL PRIVILEGES ON ruianweb.* TO 'ruiand'@'Server_B_IP'; FLUSH PRIVILEGES;
2. The “Latency Tax” (Hard Lesson)
We attempted to keep the Database on Server A while the Code lived on Server B.
- Observation: The website became extremely sluggish.
- The Math: 100 SQL queries at 50ms latency = 5 seconds to load one page.
- Verdict: Database proximity is non-negotiable for “chatty” apps like WordPress. Cross-server SQL should only be a temporary bridge.
3. The Hostfile Hack
To decouple code from infrastructure, we mapped the DB host at the OS level on Server B:
Bash
# /etc/hosts [Server_A_IP] mysql_remote_host # wp-config.php define( 'DB_HOST', 'mysql_remote_host' );
Phase 5: Security Hardening (Post-Migration)
Once the connection was verified, we revoked root remote access to minimize the attack surface.
Revoke Rule:
DROP USER 'root'@'Server_B_IP'; FLUSH PRIVILEGES;
Always use a dedicated app user (ruiand) instead of root for web applications.