VPS Snaps

We earn commissions when you shop through the links below.

How to move a server to a new cloud provider with minimal downtime

Copy everything to the new server while the old one keeps serving, then take a short maintenance window: stop writes, run a final rsync pass and a final database dump, and point DNS at the new IP. Lower the DNS TTL a day or more ahead so the switch reaches visitors within minutes, and leave the old server intact until the new one has run cleanly for a few days.

10 min readUpdated Checked against official documentation

The plan

  1. A week before: inventory the old server and what points at its IP.
  2. At least one old TTL before the move: lower the TTL of the records you will change.
  3. Build the new server to match, copy files with a first rsync pass, load a test copy of the databases.
  4. Test it under the real hostname, from your own computer only.
  5. Cutover: stop writes, final dump and restore, final rsync pass, switch DNS.
  6. Verify, then keep the old server stopped but intact for a few days.

Only step 5 is downtime. The final rsync sends only what changed since the first pass, so for most sites the window is the time a database dump and restore take.

Moving one WordPress site rather than a whole server? See how to move a WordPress site to a new host.

Inventory the old server

WhatCommand
OS releasecat /etc/os-release
What listens on which portsudo ss -tlnp (-t TCP, -l listening, -n numeric ports, -p the process)
Running servicessystemctl list-units --type=service --state=running
Packages you installedapt-mark showmanual
Runtime versionsnginx -v, php -v, node -v, psql --version, mysql --version
Scheduled jobssudo crontab -u www-data -l for each user, ls /etc/cron.d, systemctl list-timers
Accounts that own filesawk -F: '$3 >= 1000 && $3 < 65534' /etc/passwd and id www-data
Certificatessudo certbot certificates
Firewallsudo ufw status verbose
Disk usedf -h and sudo du -sh /var/www /srv /home

The Linux server backup checklist lists the paths that hold this state. Then hunt for the old IP outside the server, because each of these breaks quietly after the move:

  • DNS records besides the website: mail, API and admin hostnames, and SPF TXT records with an ip4: entry.
  • Allow-lists: managed database firewalls, payment and webhook providers, partner APIs, other servers' firewalls.
  • Reverse DNS (PTR) for a server that sends mail, set at the new provider.
  • Monitoring, backups and deploy pipelines that connect by IP.

And in the server's own files:

Terminal
sudo grep -rn '203.0.113.10' /etc /var/www

Lower the DNS TTL first

Resolvers cache a record for its TTL. With a TTL of 3600, some visitors keep reaching the old server for up to an hour after you change the record; with 86400, up to a day. So lower the TTL to 300 seconds at least one old TTL before the cutover.

Worked example: the A record has a TTL of 86400. Set it to 300 on Monday morning. By Tuesday morning every cache has dropped the old value, and from then on a change reaches most visitors within 5 minutes. Raise the TTL again a few days after the move.

  • Cloudflare: proxied records always use Auto (300 seconds), and visitors connect to Cloudflare's addresses, not yours, so switching the origin is an edit to the record. DNS-only records take 60 seconds to 1 day (30 seconds on Enterprise).
  • DigitalOcean's docs suggest 300 or 600 seconds for records you change often.

Build the new server to match

  • Same OS release, or at least the same major versions of PHP, Node, PostgreSQL or MySQL. Upgrade before or after the move, not during it, so you know which change broke what.
  • Packages from your list, firewall rules and SSH keys in place before any data arrives.
  • A disk at least as large as the data plus a database dump.
  • The same users and groups, with the same numeric IDs:
Terminal
sudo groupadd -g 1001 deploy
Terminal
sudo useradd -u 1001 -g deploy -m -s /bin/bash deploy

The numbers matter because files belong to a UID, not a name. rsync's --numeric-ids copies owners as numbers: if deploy is 1001 on the old server and 1002 on the new one, the files arrive owned by whoever is 1001 there. Without --numeric-ids, rsync maps owners by name, which is the safer choice if you could not match the numbers. Root (0) is never mapped either way.

Still choosing where to move? A DigitalOcean Droplet is one option, and VPS Snaps can schedule its snapshots from the day you create it.

Create a DigitalOcean account

Affiliate link — we earn a commission if you sign up.

Copy the files: the first pass

Run rsync on the new server and pull from the old one. The old server is only read, and it never holds credentials for the new one.

Terminal
sudo rsync -aHAX --numeric-ids --info=progress2 [email protected]:/var/www/ /var/www/
FlagWhat it does
-aArchive mode, -rlptgoD: recurse, keep symlinks, permissions, times, group, owner, devices.
-HKeep hard links as hard links. -a does not.
-AKeep ACLs (implies -p).
-XKeep extended attributes.
--numeric-idsKeep owners by number instead of mapping names.
--info=progress2One progress line for the whole transfer instead of a line per file.

The trailing slashes copy the contents of /var/www/ into /var/www/. Repeat for each path from your inventory: /srv, /home, /opt, /etc/letsencrypt, and wherever the app writes. If root cannot log in over SSH, connect as a sudo user and run rsync as root on the far side; that user needs passwordless sudo for rsync, since there is no terminal to type a password into:

Terminal
sudo rsync -aHAX --numeric-ids --rsync-path="sudo rsync" [email protected]:/var/www/ /var/www/

We ran this on a test tree with rsync 3.2.7: hard-linked files arrived as hard links and owners kept their numbers.

Do not copy all of /etc over the new server's /etc. /etc/fstab, /etc/hostname, the network configuration and /etc/machine-id describe the old machine; copying them can leave the new one unable to boot or unreachable. Copy the files you changed, one by one.

For scp and rsync between two servers, including which side needs the key and how to resume a large copy, see how to copy files between servers.

Copy the databases

Never rsync a running database's data directory: the copy is inconsistent. Dump and restore, once now to test the new server and again at cutover. PostgreSQL, on the old server: a custom-format dump of each database, plus the roles, which pg_dump leaves out.

Terminal
sudo -u postgres pg_dump -Fc -f /var/backups/appdb.dump appdb
Terminal
sudo -u postgres pg_dumpall --globals-only -f /var/backups/globals.sql

On the new server, after copying both files across with rsync: load the roles, create the database, restore with four parallel jobs.

Terminal
sudo -u postgres psql -f /var/backups/globals.sql postgres
Terminal
sudo -u postgres createdb -O appuser appdb
Terminal
sudo -u postgres pg_restore -j 4 -d appdb /var/backups/appdb.dump

MySQL or MariaDB: --single-transaction takes a consistent snapshot of InnoDB tables without locking them, --routines and --events add stored procedures, functions and scheduled events, and --databases adds CREATE DATABASE, so the restore needs no database name.

Terminal
sudo mysqldump --single-transaction --routines --events --databases appdb > /var/backups/appdb.sql
Terminal
sudo mysql < /var/backups/appdb.sql

Create the app's database user and grants on the new server; a dump of one database does not carry accounts. More in pg_dump and pg_restore and mysqldump. A database too large to dump inside a maintenance window needs replication to the new server instead, promoted at cutover: a project of its own.

Test it before anyone else sees it

Send only your own requests to the new server. curl's --resolve connects to an address you choose while keeping the real hostname, so TLS and virtual hosts behave as they will after the switch. -w '%{http_code}\n' prints just the status code:

Terminal
curl -sS -o /dev/null -w '%{http_code}\n' --resolve example.com:443:198.51.100.20 https://example.com/

To click through in a browser, add 198.51.100.20 example.com www.example.com to /etc/hosts on your computer (on Windows, C:\Windows\System32\drivers\etc\hosts) and remove it afterwards. Try logins, uploads, outgoing email and scheduled jobs, and fix problems while the old server still serves everyone.

TLS: copied with rsync, /etc/letsencrypt keeps its symlinks from live/ into archive/, so the certificates work before DNS moves. Certbot's renewal files name the authenticator and installer that issued each certificate, so install the same plugin (the nginx one, say) on the new server. New certificates instead would need DNS to point at the new server first, unless you use DNS validation.

The cutover

  1. Put the site in maintenance mode or stop the app on the old server, so nothing writes. Stop cron and queue workers there too (sudo systemctl stop cron; the service is crond on RHEL-family systems), or they keep sending email and writing to the old database.
  2. Take the final database dump, copy it, and restore it on the new server.
  3. Preview, then run, the final rsync pass with --delete.
  4. Start the app, cron and workers on the new server.
  5. Change the DNS records to the new IP.
  6. Check what a public resolver answers, and watch the new server's access log fill up.
Terminal
sudo rsync -aHAX --numeric-ids --delete -n -i [email protected]:/var/www/ /var/www/

--delete removes files on the new server that no longer exist on the old one, such as uploads deleted since the first pass, and also anything you created only on the new server in that tree. So preview with -n (dry run) and -i (itemize). Our test run showed:

Output
*deleting   html/error.log
.d..t...... html/
>f..t...... html/index.php
>f+++++++++ html/new-page.php

*deleting would be removed, >f..t...... is a changed file to update, >f+++++++++ a new file. If the list looks right, run the same command without -n -i. For step 6, ask a public resolver which address it now returns:

Terminal
dig +short example.com @1.1.1.1

Verify nothing was lost

Files: a checksum dry run compares every file's contents and changes nothing. No output means the trees match.

Terminal
sudo rsync -aHAX --numeric-ids --delete -n -c -i [email protected]:/var/www/ /var/www/

-c compares checksums instead of size and modification time. In our test, a file altered without changing its size or time showed up only with -c:

Output
>fc........ html/new-page.php

It reads every file on both servers, so run it once, after cutover. For databases, count rows in the tables that matter on both servers:

Terminal
sudo -u postgres psql -d appdb -Atc 'select count(*) from orders'
Terminal
sudo mysql -N -e 'select count(*) from appdb.orders'

Then smoke-test what users do: the home page and login return 200, an order or form goes through, email arrives, and the next scheduled job runs (journalctl -u cron shows it). Run sudo certbot renew --dry-run once DNS points at the new server, to prove renewals work there.

Keep a way back

Leave the old server running with its app and cron stopped for a few days. Do not delete it, and do not start its app again: it would take writes the new server never sees.

In the first hour, rolling back is switching DNS back. After that, the new server holds orders and uploads the old one lacks, so rolling back means copying them back first. Decide in advance how long you will try to fix forward, and what makes you roll back. When the old server's maintenance page stops getting visits and backups run on the new one, take a last snapshot of the old server and delete it.

If you reuse the hostname for SSH, clients will warn that the host key changed. Either copy /etc/ssh/ssh_host_* from the old server and restart SSH before anyone connects, or remove the old key on each client:

Terminal
ssh-keygen -R server.example.com

Common problems

ProblemCause and fix
Files owned by the wrong userUIDs differ between the servers and you used --numeric-ids. Fix with chown, or match the numbers and copy again.
rsync fails at once with --rsync-path="sudo rsync"sudo is asking for a password nobody can type. Allow that user to run /usr/bin/rsync without a password.
Outgoing email rejectedSPF still names the old IP, reverse DNS is not set, or the new provider blocks outbound port 25 on new accounts. Fix the record, set the PTR, or ask the provider.
Cron jobs ran twiceBoth servers ran them during the overlap. Stop cron on the old server at the start of the cutover.

Frequently asked questions

How long does a server migration take?
Copying takes as long as your data and bandwidth need, while the old server stays live. Downtime is only the final pass and database restore, usually minutes.
Should I copy /etc/letsencrypt or get new certificates?
Copy it with rsync -a, so the symlinks survive and the site has a valid certificate the moment DNS switches. Then run certbot renew --dry-run on the new server.
Do I have to stop the old server during the migration?
Only its writes, and only during the cutover window. Copy while it runs, stop the app and cron for the final pass, then keep it stopped but intact as your way back.

How this was checked

Commands, limits and prices were checked against these official pages, on October 3, 2026: