Migrating data between VPS servers is a task many administrators find stressful, fearing extended downtime or data loss in transit. With rsync, the standard Linux file synchronization tool, you can move gigabytes of data safely and efficiently — because rsync only transfers changed data (delta sync), you can run it multiple times to gradually sync your server, leaving only a brief maintenance window for the final cutover.
This guide walks through every step: setting up SSH keys for passwordless authentication, running rsync to transfer web files, migrating MySQL databases, moving Nginx or Apache configuration, and finally performing a DNS cutover with minimal downtime. The instructions apply equally to Ubuntu, Debian, and CentOS/Rocky Linux.
Understanding rsync Before You Start
rsync (Remote Sync) is a command-line tool standard on Linux that synchronizes files and directories between two machines over SSH. Its core strength is the "delta transfer algorithm" — it analyzes differences between source and destination and transmits only the changed portions, not entire files. This makes repeated syncs progressively faster and ideal for server migration workflows.
Key advantages of rsync over scp for migration work:
- Delta sync — only changed data is transferred after the first run, so subsequent runs are much faster
- Preserve metadata — the
-aflag preserves permissions, ownership, groups, and timestamps in full - Exclude patterns — flexible
--excludeoption to skip logs, cache, temp files, and other unnecessary data - Progress display —
--progressshows transfer rate, file size, and estimated time remaining - Dry-run mode — the
-nflag simulates a sync without transferring anything, so you can verify what will happen - Resumable transfers — running the same command again after a dropped connection resumes from where it left off
Preparing the Destination VPS and SSH Key Authentication
Before running rsync, set up SSH key authentication between the old VPS (source) and the new VPS (destination) so rsync can log in without a password. This is essential if you plan to schedule automatic cron-based syncs.
Setting Up SSH Keys from the Old VPS to the New VPS
# On the old VPS (source) — generate a key pair if you don't have one ssh-keygen -t ed25519 -C "migration-key" -f ~/.ssh/migration_key -N "" # Copy the public key to the new VPS ssh-copy-id -i ~/.ssh/migration_key.pub root@NEW_VPS_IP # Test that login works without a password prompt ssh -i ~/.ssh/migration_key root@NEW_VPS_IP "echo OK"
From this point on, rsync commands use -e "ssh -i ~/.ssh/migration_key" to specify the key. On the new VPS, make sure rsync is installed:
# Ubuntu / Debian apt update && apt install -y rsync # CentOS / Rocky Linux yum install -y rsync
rsync Flag Reference
Choosing the right flags is critical for a clean migration. The table below covers the most commonly used options:
| Flag | Meaning | When to use |
|---|---|---|
-a |
Archive mode — includes -rlptgoD | All migration runs — preserves metadata |
-v |
Verbose — lists each file being synced | Verifying what is being transferred |
--progress |
Shows transfer speed and per-file progress | Large file transfers |
--delete |
Removes files at destination not in source | Final sync only — use with caution |
--exclude |
Skips specified files or directories | Filtering out logs, cache, tmp, .git |
-z |
Compresses data during transfer | Slow networks or many text files |
-n |
Dry run — shows what would be synced | Testing before every real run |
--partial |
Keeps partially transferred files for resume | Very large files or unstable networks |
Step 1 — Sync Website Files (Initial Sync)
Begin by syncing your website files from the old VPS to the new one. Run this initial sync while services are still running on the old server — you are copying the bulk of data with no downtime at this stage. Do not use --delete yet.
# Initial file sync (run on the old VPS) rsync -avz --progress \ --exclude='*.log' \ --exclude='*.tmp' \ --exclude='cache/' \ --exclude='.git/' \ -e "ssh -i ~/.ssh/migration_key" \ /var/www/html/ \ root@NEW_VPS_IP:/var/www/html/
After the initial sync completes, verify the data size on both sides to confirm everything transferred:
# Check data size on the source du -sh /var/www/html/ # Check data size on the destination ssh -i ~/.ssh/migration_key root@NEW_VPS_IP "du -sh /var/www/html/"
Step 2 — Migrate the MySQL Database
MySQL data must be migrated separately from rsync. Copying MySQL data files directly while the service is running risks corruption. The correct method is to dump using mysqldump on the source, transfer the dump file, and import on the new server.
Dump All Databases from the Old VPS
# Dump all databases to a single file mysqldump --all-databases --single-transaction \ --routines --events --triggers \ -u root -p > /root/all_databases_backup.sql # Or dump individual databases (recommended for many databases) mysqldump --single-transaction --routines \ -u root -p mywebsite_db > /root/mywebsite_db.sql
Transfer the Dump File to the New VPS
# Transfer via rsync rsync -avz --progress \ -e "ssh -i ~/.ssh/migration_key" \ /root/all_databases_backup.sql \ root@NEW_VPS_IP:/root/ # Alternative: pipe directly via SSH (saves local disk space) mysqldump --all-databases --single-transaction \ --routines --events --triggers \ -u root -p | \ ssh -i ~/.ssh/migration_key root@NEW_VPS_IP \ "mysql -u root -p"
Import on the New VPS
# On the new VPS — import the dump mysql -u root -p < /root/all_databases_backup.sql # Verify all databases are present mysql -u root -p -e "SHOW DATABASES;"
Important tip: Before importing on the new VPS, verify that the MySQL version is equal to or newer than the old server. Importing a dump created on a newer MySQL version into an older one may fail. Run mysql --version on both servers and compare before proceeding.
Step 3 — Transfer Web Server Configuration
Web server configuration files must be transferred carefully. Pay attention to SSL certificate paths and any hardcoded IP addresses or directory paths that may differ between servers.
Migrating Nginx Configuration
# Transfer Nginx virtual host configs rsync -avz --progress \ -e "ssh -i ~/.ssh/migration_key" \ /etc/nginx/sites-available/ \ root@NEW_VPS_IP:/etc/nginx/sites-available/ rsync -avz --progress \ -e "ssh -i ~/.ssh/migration_key" \ /etc/nginx/snippets/ \ root@NEW_VPS_IP:/etc/nginx/snippets/ # On the new VPS — enable the site and test config ssh -i ~/.ssh/migration_key root@NEW_VPS_IP \ "ln -s /etc/nginx/sites-available/mysite.conf \ /etc/nginx/sites-enabled/ && nginx -t"
Migrating Apache Configuration
# Transfer VirtualHost configs rsync -avz --progress \ -e "ssh -i ~/.ssh/migration_key" \ /etc/apache2/sites-available/ \ root@NEW_VPS_IP:/etc/apache2/sites-available/ # On the new VPS — enable site and check syntax ssh -i ~/.ssh/migration_key root@NEW_VPS_IP \ "a2ensite mysite.conf && apache2ctl configtest"
Migrating Let's Encrypt SSL Certificates
# Transfer all Certbot certificate data rsync -avz --progress \ -e "ssh -i ~/.ssh/migration_key" \ /etc/letsencrypt/ \ root@NEW_VPS_IP:/etc/letsencrypt/ # On the new VPS — verify certificates ssh -i ~/.ssh/migration_key root@NEW_VPS_IP \ "certbot certificates"
After DNS cutover, run certbot renew --dry-run on the new VPS to confirm that auto-renewal works. Let's Encrypt HTTP challenges require DNS to point at the new IP before they can succeed.
Step 4 — Final Sync and Cutover
The final sync ensures both servers have identical data immediately before cutover. This step contains the actual downtime — but since rsync only sends changed data, it typically completes in a few minutes if the initial sync was done recently.
# Final cutover sequence (run in order) # 1. Stop the web server on the old VPS systemctl stop nginx # or: systemctl stop apache2 # 2. Flush and take a final MySQL dump mysql -u root -p -e "FLUSH TABLES WITH READ LOCK;" mysqldump --all-databases --single-transaction \ --routines --events --triggers \ -u root -p > /root/final_dump.sql mysql -u root -p -e "UNLOCK TABLES;" # 3. Final rsync with --delete to achieve exact mirror rsync -avz --progress --delete \ --exclude='*.log' \ --exclude='*.tmp' \ --exclude='cache/' \ -e "ssh -i ~/.ssh/migration_key" \ /var/www/html/ \ root@NEW_VPS_IP:/var/www/html/ # 4. Import the final dump on the new VPS ssh -i ~/.ssh/migration_key root@NEW_VPS_IP \ "mysql -u root -p < /root/final_dump.sql" # 5. Start services on the new VPS ssh -i ~/.ssh/migration_key root@NEW_VPS_IP \ "systemctl start nginx && systemctl start php8.2-fpm"
Pre-Cutover Verification Checklist
Before changing DNS, test the website on the new VPS by temporarily editing your local machine's /etc/hosts file to route the domain name to the new IP address:
# Add to /etc/hosts on your local machine (not on the VPS) # Replace NEW_VPS_IP with the actual IP of the new server NEW_VPS_IP mydomain.com www.mydomain.com
Items to verify before DNS cutover:
- Website loads correctly in a browser using the modified hosts file
- Admin panel or CMS login works successfully
- Database queries function correctly — check login, data retrieval
- All images and media files load without errors
- If you have a mail server, verify MX records and mail delivery
systemctl status nginx mysql php8.2-fpmshows all services as active- Error logs are clean:
tail -50 /var/log/nginx/error.log
DNS Cutover and Post-Migration Verification
Once all checks pass, reduce your DNS record TTL 24–48 hours in advance if possible to speed up propagation. Then update the A record to point to the new VPS IP address. Monitor propagation using dig:
# Verify DNS propagation after the change dig +short mydomain.com A nslookup mydomain.com 8.8.8.8 # Check from multiple resolvers dig mydomain.com @1.1.1.1 A +short dig mydomain.com @8.8.8.8 A +short
Keep the old VPS running for at least 7 days after cutover. This provides a safety net for rollback and a window to retrieve any data you may have missed. Once you are confident the new server is fully operational and stable, you can decommission the old one.
Frequently Asked Questions
What is the difference between rsync and scp for VPS migration?
rsync only transfers changed data (delta sync), making it efficient for repeat runs — each subsequent sync is faster because only new or modified files are sent. It also supports --exclude patterns and preserves permissions, ownership and timestamps with the -a flag. scp copies entire files every time with no delta detection, making it suitable only for small one-off transfers. For VPS migration involving gigabytes of data, rsync is the better choice because you can run a final sync during a short maintenance window to minimize downtime.
Do I need to stop services on the old VPS before running rsync?
No, you do not need to stop services for the initial sync. Run the first rsync while services are running to transfer the bulk of data with no downtime. Only for the final cutover sync should you stop Nginx/Apache and MySQL, then run rsync one last time to capture changes since the first sync. This approach limits total downtime to just a few minutes during the final sync and DNS propagation.
Will rsync preserve file permissions and ownership during migration?
Yes, when you use the -a (archive) flag, rsync preserves permissions (-p), group (-g), owner (-o), and timestamps (-t). However, preserving ownership requires rsync to run as root on the destination server. If you run as a non-root user, rsync will fall back to the current user's identity. After syncing, verify with ls -la on both servers to confirm permissions and ownership match correctly.
What should I check on the new VPS before switching DNS?
Before cutting over DNS, verify at least five things: (1) Test the website by temporarily editing /etc/hosts on your local machine to point the domain to the new IP; (2) Confirm MySQL databases and users exist and can be connected to; (3) Run systemctl status for all services and verify they show active; (4) Check Nginx or Apache error logs for any 500 errors; (5) Confirm SSL certificate renewal scripts (cron) are configured on the new VPS. Only proceed with the DNS change once all checks pass.
High-Performance KVM VPS by AsiaGB
AsiaGB VPS with full root access — Docker, MySQL, Python, Node.js ready. Starting from 500 THB/month.
View VPS Plans