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:

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.