Migrating a VPS from one cloud provider to another is one of those tasks that keeps sysadmins up at night. The fear is understandable: a botched migration can mean hours of downtime, lost data, broken DNS, and angry users flooding your inbox. But here is the truth — VPS migrations do not have to mean downtime. With the right strategy, careful preparation, and a methodical approach, you can move your entire stack between providers with zero or near-zero downtime for your users.

This guide walks you through the complete process, from pre-migration planning to decommissioning your old server. Whether you are migrating a simple web application or a complex multi-service deployment, the principles remain the same. We will cover DNS strategy, data synchronization, database migration, and every step in between — with actual commands you can copy and adapt for your own migration.

Pre-Migration Planning

The single biggest cause of migration failures is insufficient planning. Before you even provision your new server, you need a complete picture of what you are migrating. Skipping this step leads to the classic scenario: everything looks perfect on the new server until you realize the cron job that processes overnight reports was never migrated, or the custom firewall rules that protect your API endpoints are missing.

Inventory Everything

Start by creating a comprehensive inventory of every component running on your current VPS. This is not something you can do from memory — you need to systematically audit your server. Here is what to document:

Document Your Stack

Create a detailed specification of your server environment. This becomes the blueprint for configuring your new server:

Measure Your Data

Understanding your data volume directly impacts your migration timeline. Run these measurements:

# Total disk usage
df -h

# Size of key directories
du -sh /var/www/ /var/lib/mysql/ /home/ /etc/ /opt/

# Database sizes (MySQL)
mysql -e "SELECT table_schema AS 'Database',
  ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size (MB)'
  FROM information_schema.tables GROUP BY table_schema;"

# Current bandwidth usage (useful for planning transfer window)
vnstat -m

These numbers tell you how long the initial data sync will take. A server with 5 GB of data on a decent connection will sync in minutes. A server with 500 GB of data might need several hours for the initial transfer. Plan accordingly — you want the initial sync to complete well before your planned cutover window.

The Zero-Downtime Migration Strategy

The key to a zero-downtime migration is running both servers simultaneously and using DNS to seamlessly redirect traffic. Rather than stopping your old server and starting the new one (which guarantees downtime), you bring the new server online in parallel, synchronize all data, then redirect traffic via DNS while both servers remain operational. Here is the complete step-by-step process.

Step 1: Provision and Configure the New Server

Spin up your new VPS with the same operating system and version as your current server. Install all the same packages, recreate user accounts, and replicate every configuration file. This is where your documentation from the planning phase pays off.

# On the new server: install the same packages
# For Debian/Ubuntu systems
sudo apt update
sudo apt install nginx php8.2-fpm php8.2-mysql php8.2-curl \
  php8.2-gd php8.2-mbstring mysql-server redis-server certbot

# Copy over configuration files (we will do a full sync later,
# but set up the basics so services can start)
# Nginx config, PHP pools, MySQL config, etc.

At this stage, do not point any live traffic at the new server. It is purely a staging environment where you are building an exact replica of your production setup. Test each service individually — make sure Nginx starts, PHP-FPM processes requests, your database server accepts connections.

Step 2: Lower DNS TTL (48 Hours Before Migration)

This is the step most people either skip or do too late. DNS records have a Time-To-Live (TTL) value that tells resolvers how long to cache the record. If your TTL is set to 86400 (24 hours), then after you change your DNS records, some users might still be directed to your old server for up to 24 hours.

At least 48 hours before your planned migration, lower the TTL on your A/AAAA records to 300 seconds (5 minutes):

# DNS record before TTL change
example.com.    86400   IN  A   203.0.113.10    ; old server

# Lower TTL to 300 seconds (do this 48+ hours before migration)
example.com.    300     IN  A   203.0.113.10    ; old server, short TTL

# After migration cutover, point to new server
example.com.    300     IN  A   198.51.100.20   ; new server

Why 48 hours in advance? Because the old TTL of 86400 means some resolvers might have cached your record for up to 24 hours. You need to wait for all those old cached entries to expire before the new shorter TTL takes effect everywhere. Giving it 48 hours provides a comfortable margin.

Step 3: Initial Data Sync with rsync

With the new server configured and ready, perform the initial bulk data transfer. This is the longest part of the migration, but because your old server is still serving traffic normally, your users experience zero impact.

Use rsync over SSH for the initial sync. The -a flag preserves permissions, ownership, timestamps, and symlinks. The -z flag compresses data during transfer, and --progress gives you visibility into the transfer:

# Initial sync of web files
rsync -azP --delete \
  -e "ssh -i /path/to/key" \
  /var/www/ \
  user@new-server-ip:/var/www/

# Sync configuration files
rsync -azP \
  -e "ssh -i /path/to/key" \
  /etc/nginx/ \
  user@new-server-ip:/etc/nginx/

rsync -azP \
  -e "ssh -i /path/to/key" \
  /etc/php/ \
  user@new-server-ip:/etc/php/

# Sync SSL certificates
rsync -azP \
  -e "ssh -i /path/to/key" \
  /etc/letsencrypt/ \
  user@new-server-ip:/etc/letsencrypt/

# Sync cron jobs
rsync -azP \
  -e "ssh -i /path/to/key" \
  /var/spool/cron/ \
  user@new-server-ip:/var/spool/cron/

# Sync home directories (user configs, scripts, etc.)
rsync -azP \
  -e "ssh -i /path/to/key" \
  /home/ \
  user@new-server-ip:/home/

This initial sync may take anywhere from minutes to hours depending on your data volume and network speed. That is perfectly fine — let it run. Your old server continues serving traffic uninterrupted. Once the initial sync completes, the delta between the two servers is small: only changes that occurred during the sync period.

Step 4: Final Delta Sync

Just before the DNS cutover, run rsync one more time. Because the initial bulk transfer is already done, this delta sync only transfers files that changed since the first sync. It typically completes in seconds or a few minutes at most:

# Final delta sync — only transfers changed files
rsync -azP --delete \
  -e "ssh -i /path/to/key" \
  /var/www/ \
  user@new-server-ip:/var/www/

At this point, the file systems on both servers are nearly identical. The only gap is the database, which we handle in the next step.

Step 5: Database Migration

Database migration requires the most care because databases are stateful — new data is being written to them continuously. We cover the detailed database strategies in the next section, but the high-level approach is:

  1. Take a consistent dump from the old server’s database
  2. Import it into the new server’s database
  3. If using replication, let the replica catch up to the primary
  4. Verify data integrity on the new server

For applications that can tolerate a brief write freeze (a few seconds to a few minutes), the simplest approach is to put your application in maintenance mode, take the final database dump, import it on the new server, then proceed with the DNS cutover. For applications that cannot tolerate any interruption, set up real-time replication between the old and new servers.

Step 6: DNS Cutover

With both servers fully synchronized, update your DNS records to point to the new server’s IP address. Because you lowered the TTL to 300 seconds in Step 2, within 5 minutes most clients will be directed to the new server:

# Update your DNS A record
example.com.    300     IN  A   198.51.100.20   ; new server IP

# If you have www subdomain
www.example.com. 300    IN  A   198.51.100.20   ; new server IP

# Update any other subdomains
api.example.com. 300    IN  A   198.51.100.20   ; new server IP

This is the moment of truth, but because both servers have identical data and both are running, users connecting to either server get a working experience. There is no downtime window — just a gradual transition as DNS caches expire and clients start resolving to the new IP.

Step 7: Monitor Both Servers

After the DNS cutover, keep both servers running and monitored. Even with a low TTL, some resolvers (particularly corporate DNS servers and certain ISPs) may cache records longer than the TTL specifies. During this overlap period:

# Monitor real-time access on both servers
# On old server:
tail -f /var/log/nginx/access.log | grep -c ""

# On new server:
tail -f /var/log/nginx/access.log | grep -c ""

# Watch for errors on the new server
tail -f /var/log/nginx/error.log
tail -f /var/log/php8.2-fpm.log

Step 8: Decommission the Old Server

Wait 48 to 72 hours after the DNS cutover before shutting down the old server. This provides ample time for even the most stubborn DNS caches to expire. Before decommissioning:

Migrating Databases Without Data Loss

Database migration deserves its own section because it is the most risk-prone part of any VPS migration. Files are relatively straightforward — rsync handles them elegantly. But databases have open transactions, replication state, binary logs, and data integrity constraints that demand careful handling.

MySQL / MariaDB Migration

For MySQL and MariaDB, you have two primary strategies: logical dumps and replication.

Option A: mysqldump with --single-transaction

The --single-transaction flag takes a consistent snapshot of InnoDB tables without locking them, which means your application continues to serve read and write requests during the dump:

# On the old server: create a consistent dump
mysqldump --single-transaction --routines --triggers \
  --all-databases -u root -p > /tmp/full_dump.sql

# Transfer the dump to the new server
rsync -azP /tmp/full_dump.sql user@new-server-ip:/tmp/

# On the new server: import the dump
mysql -u root -p < /tmp/full_dump.sql

# Verify the import
mysql -e "SHOW DATABASES;"
mysql -e "SELECT COUNT(*) FROM your_database.your_main_table;"

The limitation of this approach is that any writes to the old database after the dump was taken will not be present on the new server. For low-traffic applications or those that can tolerate a brief maintenance window, this is the simplest and most reliable method.

Option B: MySQL Replication

For applications requiring truly zero data loss during cutover, set up the new server as a replica of the old server. This keeps the new database continuously synchronized until you are ready to promote it:

# On the old server: enable binary logging in my.cnf
# [mysqld]
# server-id = 1
# log-bin = mysql-bin
# binlog-format = ROW

# On the old server: create a replication user
mysql -e "CREATE USER 'repl'@'new-server-ip' IDENTIFIED BY 'secure_password';"
mysql -e "GRANT REPLICATION SLAVE ON *.* TO 'repl'@'new-server-ip';"
mysql -e "FLUSH PRIVILEGES;"

# On the old server: get the current binary log position
mysql -e "SHOW MASTER STATUS\G"
# Note the File and Position values

# On the new server: configure as replica
mysql -e "CHANGE MASTER TO
  MASTER_HOST='old-server-ip',
  MASTER_USER='repl',
  MASTER_PASSWORD='secure_password',
  MASTER_LOG_FILE='mysql-bin.000001',
  MASTER_LOG_POS=154;"

mysql -e "START SLAVE;"
mysql -e "SHOW SLAVE STATUS\G"
# Verify Slave_IO_Running and Slave_SQL_Running are both 'Yes'

When you are ready for the cutover, stop writes to the old server, verify the replica has caught up (Seconds_Behind_Master: 0), then promote the new server by stopping the replica and directing your application to it.

PostgreSQL Migration

PostgreSQL offers similar options with its own tooling.

Option A: pg_dump / pg_restore

# On the old server: create a custom-format dump (supports parallel restore)
pg_dump -Fc -U postgres your_database > /tmp/db_backup.dump

# Transfer to new server
rsync -azP /tmp/db_backup.dump user@new-server-ip:/tmp/

# On the new server: create the database and restore
createdb -U postgres your_database
pg_restore -U postgres -d your_database -j 4 /tmp/db_backup.dump
# -j 4 runs 4 parallel jobs for faster restore

# Verify
psql -U postgres -d your_database -c "SELECT COUNT(*) FROM your_main_table;"

Option B: Streaming Replication with pg_basebackup

# On the old server: configure postgresql.conf
# wal_level = replica
# max_wal_senders = 3
# wal_keep_size = 1GB

# On the old server: add replication entry to pg_hba.conf
# host replication replicator new-server-ip/32 md5

# Create replication user
psql -U postgres -c "CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'secure_password';"

# On the new server: take a base backup
pg_basebackup -h old-server-ip -U replicator -D /var/lib/postgresql/16/main \
  -Fp -Xs -P -R

# Start PostgreSQL on the new server — it will automatically
# connect to the old server and stream WAL records
sudo systemctl start postgresql

With streaming replication active, the new PostgreSQL instance stays continuously in sync with the old one. At cutover time, promote the new server with pg_ctl promote or SELECT pg_promote(); and redirect your application.

Common Migration Pitfalls and How to Avoid Them

Even well-planned migrations can hit unexpected issues. Here are the most common pitfalls we see and how to avoid them.

Forgotten Cron Jobs

Cron jobs are invisible until they stop running. That nightly database optimization, the weekly report generator, the log rotation script, the SSL certificate auto-renewal — all of these live in cron and are easy to overlook. Before declaring your migration complete, compare the crontabs on both servers line by line:

# On old server: export all cron jobs
for user in $(cut -f1 -d: /etc/passwd); do
  echo "=== $user ===" && crontab -u $user -l 2>/dev/null
done > /tmp/all_crons_old.txt

# Compare with new server
diff /tmp/all_crons_old.txt /tmp/all_crons_new.txt

Missing SSL Certificates

If you use Let’s Encrypt with certbot, the certificates and renewal configuration live in /etc/letsencrypt/. Make sure you sync this entire directory, including the renewal hooks. After migration, test renewal with certbot renew --dry-run to verify it works on the new server. If you are using manually installed certificates, make sure the private key, certificate, and CA bundle are all present on the new server and that your web server configuration points to the correct paths.

Email Delivery Problems

If your VPS sends transactional emails (password resets, order confirmations, notifications), the new server’s IP address may not have the same reputation as the old one. Before cutover:

DNS Propagation Delays

Despite setting a low TTL, some resolvers ignore TTL values or enforce minimums. Enterprise DNS servers, some ISPs, and certain countries are notorious for this. Mitigations include:

# Check DNS propagation from different nameservers
dig @8.8.8.8 example.com A +short      # Google
dig @1.1.1.1 example.com A +short      # Cloudflare
dig @9.9.9.9 example.com A +short      # Quad9
dig @208.67.222.222 example.com A +short # OpenDNS

File Permission Issues After rsync

Rsync preserves permissions when using the -a flag, but user and group IDs (UIDs/GIDs) must match between servers. If the www-data user has UID 33 on the old server but UID 1001 on the new server, your files will have the wrong ownership even though rsync preserved the numeric IDs. Verify and fix after syncing:

# Verify web file ownership
ls -la /var/www/

# Fix ownership if needed
chown -R www-data:www-data /var/www/html/

# Verify correct permissions on sensitive files
chmod 600 /var/www/html/.env
chmod 644 /var/www/html/index.php

Hardcoded IP Addresses

Search your application code and configuration files for the old server’s IP address. Applications that connect to localhost or use hostnames are fine, but any hardcoded IP references will break after migration. Common places to check:

# Search for hardcoded old IP across your codebase
grep -r "203.0.113.10" /var/www/ /etc/nginx/ /etc/ --include="*.conf" --include="*.php" --include="*.env" --include="*.py" --include="*.yml"

Choosing the Right Server for Your Migration

When you migrate to a new provider, it is the perfect opportunity to right-size your infrastructure. Instead of blindly replicating your old setup, evaluate what you actually need based on your workload. Here is a decision framework to help you pick the right tier for your migration destination.

Migrating a Dev/Staging Server

If you are moving a development or staging environment — something that does not serve production traffic and where occasional resource contention is acceptable — a Cloud VPS is the most cost-effective option. Starting from $3.99/mo, you get flexible resource allocation, hourly billing, and the ability to spin up or tear down instances on demand. It is ideal for testing environments, CI/CD build servers, and internal tools.

Migrating Production with Guaranteed Resources

For production workloads where performance consistency matters, a Cloud VDS (Dedicated VPS) starting from $19.80/mo gives you dedicated CPU cores and guaranteed RAM that are not shared with other tenants. This eliminates the noisy-neighbor problem that plagues shared virtualization. If your application has predictable resource requirements and you need consistent performance under load, this is the tier to target.

Migrating and Never Wanting to SSH Again

If server management is not your core competency — or you simply do not want to deal with OS updates, security patches, monitoring, and backup configuration — Managed Cloud Servers starting from $27.79/mo hand all of that off to a dedicated operations team. You get a fully managed environment with proactive monitoring, automated backups, and 24/7 support. Focus on your application, not your infrastructure.

Migrating Mission-Critical Infrastructure

For workloads where downtime is measured in lost revenue — e-commerce platforms, financial applications, healthcare systems, or SaaS products with SLA commitments — Managed Cloud Dedicated Servers starting from $76.19/mo provide the highest tier of performance and reliability. Dedicated physical resources, high-availability architecture, and a managed operations team ensure your critical applications stay online.

Not sure which tier is right for your migration?

MassiveGRID’s team will assess your current workload — CPU utilization, memory footprint, storage I/O patterns, and traffic profiles — and recommend the right configuration for your needs. And the best part? They will migrate you for free. No downtime, no hassle, no surprise bills. Talk to the migration team to get started.

Pre-Migration Checklist

Before you start your migration, run through this checklist to make sure nothing falls through the cracks:

Task Status
Full server inventory documented (apps, services, ports)
Database sizes measured and migration strategy chosen
DNS zone exported and TTL lowered to 300s
New server provisioned with matching OS and packages
Initial rsync completed
SSL certificates transferred and verified
Cron jobs migrated and verified
Firewall rules replicated
Email delivery tested from new server
Application tested end-to-end on new server (using /etc/hosts)
Hardcoded IPs searched and updated
Rollback plan documented
Team notified of migration window

Conclusion

Migrating a VPS to a new provider does not have to be a white-knuckle experience. The zero-downtime strategy outlined in this guide — lowering DNS TTL in advance, running parallel servers, using rsync for incremental file synchronization, and carefully migrating databases with either dumps or replication — has been battle-tested across thousands of migrations of every size and complexity.

The critical success factors are preparation and patience. Invest the time upfront in documenting your stack, inventorying every service and configuration file, and testing your new environment thoroughly before touching DNS. Then, when you flip the switch, the transition is invisible to your users.

Whether you are migrating to reduce costs, improve performance, get better support, or move to a provider with more robust infrastructure, the process is the same. Follow the steps, use the checklist, and take your time with the cutover. Your users will never know you moved.

Ready to migrate? Explore MassiveGRID’s Cloud Server options and find the right fit for your workload. Need help? The team handles migrations daily and offers free migration assistance for all new accounts.