VPS Migration Checklist: Move Your Server Without Downtime or Data Loss

VPS Migration Checklist: Move Your Server Without Downtime or Data Loss

Migrating a VPS — whether to a different provider, a larger plan, or a different region — involves more than copying files. Databases must be transferred consistently, configurations must be replicated, DNS must be cut over with minimal downtime, and the old server must remain available as a fallback until the new one is verified. This checklist guides you through a safe, systematic migration.

Migration Overview

<code">Phase 1: Prepare new VPS (install software, copy config)
Phase 2: Transfer data (files, databases)
Phase 3: Test on new VPS (verify everything works)
Phase 4: DNS cutover (point domain to new server)
Phase 5: Monitor (watch for issues)
Phase 6: Decommission old server (after confirmation period)

Phase 1: Inventory the Old Server

<code"># Document everything installed and running
echo "=== OS ===" && cat /etc/os-release
echo "=== Running services ===" && systemctl list-units --type=service --state=running
echo "=== Open ports ===" && sudo ss -tlnp
echo "=== Installed packages ===" && dpkg -l | grep "^ii" > /tmp/packages.txt
echo "=== Nginx sites ===" && ls /etc/nginx/sites-enabled/
echo "=== Cron jobs ===" && crontab -l && sudo crontab -l
echo "=== Docker containers ===" && docker ps -a
echo "=== Docker volumes ===" && docker volume ls
echo "=== Disk usage ===" && df -h && du -sh /var/www/* /opt/* 2>/dev/null
echo "=== Environment files ===" && find / -name ".env" 2>/dev/null | grep -v proc

Phase 2: Prepare the New VPS

<code"># On NEW VPS — install same software stack
sudo apt update && sudo apt upgrade -y

# Install same packages (from list exported above)
# Do NOT install from the package list directly — use it as reference
sudo apt install -y nginx certbot python3-certbot-nginx \
    docker.io docker-compose-plugin \
    nodejs npm \
    postgresql mariadb-server redis-server \
    fail2ban ufw

# Copy SSH authorized keys
mkdir -p ~/.ssh
# Paste your public key:
echo "ssh-ed25519 AAAA... you@yourmachine" >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys

# Apply same sysctl tuning
# Copy /etc/sysctl.d/99-vps-performance.conf from old server
# sudo sysctl -p /etc/sysctl.d/99-vps-performance.conf

Phase 3: Transfer Files

<code"># From your LOCAL machine (or from the new VPS using rsync over SSH):
# Transfer web files
rsync -avz --progress \
    deploy@OLD_VPS_IP:/var/www/ \
    deploy@NEW_VPS_IP:/var/www/

# Transfer Nginx configuration
rsync -avz \
    deploy@OLD_VPS_IP:/etc/nginx/ \
    /tmp/nginx-backup/
# Review, then copy to new VPS:
scp -r /tmp/nginx-backup/ deploy@NEW_VPS_IP:/etc/nginx/

# Transfer application directories
rsync -avz deploy@OLD_VPS_IP:/opt/ deploy@NEW_VPS_IP:/opt/

# Transfer environment files
scp deploy@OLD_VPS_IP:/opt/myapp/.env /tmp/myapp.env
scp /tmp/myapp.env deploy@NEW_VPS_IP:/opt/myapp/.env
rm /tmp/myapp.env   # Clean up local copy

Phase 4: Transfer Databases

PostgreSQL

<code"># On OLD VPS — dump all databases
sudo -u postgres pg_dumpall | gzip > /tmp/postgres-all.sql.gz

# Transfer to new VPS
scp deploy@OLD_VPS_IP:/tmp/postgres-all.sql.gz /tmp/

# On NEW VPS — restore
gunzip -c /tmp/postgres-all.sql.gz | sudo -u postgres psql
rm /tmp/postgres-all.sql.gz

# Verify databases restored
sudo -u postgres psql -c "\l"

MariaDB/MySQL

<code"># On OLD VPS
sudo mysqldump --all-databases --single-transaction \
    --routines --triggers \
    | gzip > /tmp/mysql-all.sql.gz

scp deploy@OLD_VPS_IP:/tmp/mysql-all.sql.gz /tmp/

# On NEW VPS
gunzip -c /tmp/mysql-all.sql.gz | sudo mysql
rm /tmp/mysql-all.sql.gz

# Verify
sudo mysql -e "SHOW DATABASES;"

Docker Volumes

<code"># For each important Docker volume on OLD VPS:
docker run --rm \
    -v myapp_data:/source \
    -v /tmp:/backup \
    alpine tar czf /backup/myapp_data.tar.gz -C /source .

scp deploy@OLD_VPS_IP:/tmp/myapp_data.tar.gz /tmp/

# On NEW VPS — restore into volume:
docker volume create myapp_data
docker run --rm \
    -v myapp_data:/target \
    -v /tmp:/backup \
    alpine tar xzf /backup/myapp_data.tar.gz -C /target

Phase 5: Issue SSL Certificates on New VPS

<code"># Method A: Get new certificates (requires DNS to point to new VPS first — do after DNS cutover)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Method B: Transfer existing certificates (extend testing time before DNS cutover)
# On OLD VPS:
sudo tar czf /tmp/letsencrypt.tar.gz /etc/letsencrypt/
scp deploy@OLD_VPS_IP:/tmp/letsencrypt.tar.gz /tmp/

# On NEW VPS:
sudo tar xzf /tmp/letsencrypt.tar.gz -C /
sudo nginx -t && sudo systemctl reload nginx
# Certificates will remain valid until expiry — Certbot auto-renews

Phase 6: Test on New VPS (Before DNS Cutover)

<code"># Test by modifying your LOCAL /etc/hosts temporarily
echo "NEW_VPS_IP yourdomain.com www.yourdomain.com" | sudo tee -a /etc/hosts

# Now test in browser — you're hitting new VPS while DNS still points to old
# Verify:
# ✅ Homepage loads correctly
# ✅ Admin login works
# ✅ Database reads work (existing content visible)
# ✅ File uploads work
# ✅ Email sending works (test contact form)
# ✅ SSL certificate valid
# ✅ All critical pages load

# Remove test /etc/hosts entry when done:
sudo sed -i '/yourdomain.com/d' /etc/hosts

Phase 7: DNS Cutover

<code"># 1. Lower TTL 24 hours before cutover (to reduce propagation time)
# yourdomain.com A record: change TTL from 3600 to 300 (5 minutes)
# www.yourdomain.com A record: change TTL from 3600 to 300

# 2. Take a final database snapshot on old VPS just before cutover
sudo -u postgres pg_dumpall | gzip > /tmp/final-backup.sql.gz

# 3. Change DNS A records to NEW VPS IP
# In your DNS provider:
# yourdomain.com     A    NEW_VPS_IP
# www.yourdomain.com A    NEW_VPS_IP

# 4. Verify propagation
dig yourdomain.com A +short   # Should return NEW_VPS_IP
# Or check: https://dnschecker.org

# 5. Monitor new VPS during propagation
tail -f /var/log/nginx/access.log

Phase 8: Post-Migration Verification

<code"># Checklist — verify on new VPS:
curl -I https://yourdomain.com   # SSL valid, 200 OK
sudo systemctl status nginx fail2ban   # Services running
sudo fail2ban-client status   # Jails active
crontab -l   # Cron jobs present
docker ps   # All containers running
sudo -u postgres psql -c "\l"   # Databases present

# Check Certbot auto-renewal on new VPS:
sudo certbot renew --dry-run

Phase 9: Decommission Old VPS

<code"># Keep old VPS running for 7–14 days as fallback
# Monitor: check if any traffic still hitting old VPS
tail -f /var/log/nginx/access.log   # On OLD VPS — should quieten

# After 7–14 days with no issues:
# 1. Take a final backup from old VPS for archival
# 2. Cancel the old VPS plan in provider's control panel
# 3. Delete DNS records pointing to old IP (if any)

# Store final backup somewhere safe:
restic backup /etc /var/www /opt
# Or download to local machine:
rsync -avz deploy@OLD_VPS_IP:/var/www/ /backup/old-vps/

Common Migration Mistakes

  • Forgetting environment files: Database passwords, API keys, .env files are not in git — find and transfer every one
  • Forgetting cron jobs: Run crontab -l and sudo crontab -l on old server — don’t forget root’s crontab
  • Not lowering TTL early: Default 24-hour TTL means 24 hours of mixed traffic after DNS change
  • Cutting over without testing: Always verify on new server before changing DNS
  • Forgetting Docker volumes: Containers copy fine; volumes (persistent data) need explicit migration
  • Missing PHP/Nginx config includes: Custom php.ini settings, included Nginx snippets, upstream configs

Getting Started

Whether migrating to a larger plan, a better provider, or a different geographic location, VPS.DO’s Ubuntu VPS plans support the same software stack as any Ubuntu host — making migration straightforward. New VPS provisioning takes under 60 seconds; the migration process itself typically takes 2–6 hours depending on data volume. Hong Kong VPS plans serve APAC users; USA VPS plans serve North American audiences.

Conclusion

A systematic VPS migration checklist prevents the most common failure modes: missing environment files, high-TTL DNS delays, untested configuration, and forgotten cron jobs. The key practices are: inventory everything on the old server, test on the new server using local /etc/hosts before changing DNS, lower DNS TTL 24 hours before the cutover, and keep the old server running for 7–14 days as a fallback. A migration executed this way is low-risk and fully reversible until the final decommission step.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!