The rsync Migration Strategy
Live migration with rsync uses a two-phase approach: an initial full sync while the source server is still active, followed by a brief final sync that captures only changes since the first sync. This reduces downtime from hours to seconds or minutes.
Phase 1: Initial Sync (No Downtime)
# First full sync while source server is live
rsync -avz --progress --delete \
-e "ssh -i ~/.ssh/migrate_key" \
--exclude="/proc" --exclude="/sys" --exclude="/dev" \
--exclude="/tmp" --exclude="/run" --exclude="/mnt" \
--exclude="/var/lock" --exclude="/var/run" \
root@source-server:/var/www/ \
/var/www/
# This can take hours for large sites but source remains live
# Users experience no downtime during this phase
Phase 2: Database Sync
# Set up database replication for continuous sync
# MySQL: configure replica on destination
# Or for simpler setups, note the initial sync time
# Export database
ssh root@source "mysqldump --single-transaction --all-databases" | \
mysql -h localhost
Phase 3: Final Sync (Brief Downtime)
# 1. Put source site in maintenance mode
ssh root@source "touch /var/www/html/.maintenance"
# 2. Final database dump (captures changes since initial dump)
ssh root@source "mysqldump --single-transaction --all-databases" | \
mysql -h localhost
# 3. Final rsync (only transfers changed files - very fast)
rsync -avz --delete \
root@source-server:/var/www/ /var/www/
# 4. Update DNS to point to new server
# 5. Remove maintenance mode on new server
# Total downtime: typically 1-5 minutes
Automated Migration Script
#!/bin/bash
# /usr/local/bin/migrate-live.sh
SOURCE="root@source-ip"
DEST="/var/www"
SSH_KEY="~/.ssh/migrate_key"
RSYNC_OPTS="-avz --delete --progress"
SSH_CMD="ssh -i $SSH_KEY"
echo "=== Phase 1: Initial sync ==="
rsync $RSYNC_OPTS -e "$SSH_CMD" $SOURCE:/var/www/ $DEST/
echo "=== Phase 2: Database sync ==="
$SSH_CMD $SOURCE "mysqldump --single-transaction --all-databases" | mysql
echo "=== Phase 3: Final sync ==="
read -p "Enable maintenance mode on source? [y/N] " confirm
[ "$confirm" = "y" ] || exit 1
$SSH_CMD $SOURCE "touch /var/www/html/.maintenance"
sleep 5
rsync $RSYNC_OPTS -e "$SSH_CMD" $SOURCE:/var/www/ $DEST/
$SSH_CMD $SOURCE "mysqldump --single-transaction --all-databases" | mysql
echo "=== Migration complete ==="
echo "Update DNS and verify the new server"
rsync Options Explained
# Key flags for migration:
-a # Archive mode (preserves permissions, timestamps, symlinks)
-v # Verbose output
-z # Compress during transfer (saves bandwidth)
--delete # Remove files on dest that do not exist on source
--progress # Show transfer progress
--partial # Keep partially transferred files (resume on failure)
--bwlimit=50000 # Limit bandwidth to 50 MB/s
--checksum # Use checksums instead of mod-time (slower but more accurate)
Best Practices
- Run the initial sync multiple times before the final cutover to minimize delta
- Use --checksum for the final sync to catch any discrepancies
- Schedule the cutover during lowest traffic period
- Keep the source server running for 48h as fallback
- Lower DNS TTL well in advance of migration
- Test the new server thoroughly before DNS cutover