1. Write Down What the Old Server Does
A move goes wrong because of the thing nobody remembered. On the old server, list:
cat /etc/os-release # OS and version ls /etc/nginx/sites-enabled /etc/apache2/sites-enabled 2>/dev/null crontab -l; sudo crontab -l # scheduled jobs systemctl list-units --type=service --state=running sudo ss -tlnp # what is listening, on which ports du -sh /var/www # how much there is to copy
Also note where the site keeps uploads, which database it uses, any .env or config files with passwords and API keys, and every DNS record for the domain.
2. Lower the DNS TTL a Day Ahead
The TTL is how long resolvers cache your records. At your DNS provider, set it to 300 seconds (5 minutes) on the records you will change, then wait at least as long as the old TTL, often an hour and sometimes a day. After that, the switch reaches almost everyone within five minutes.
3. Set Up the New Server
Install the same software the old server runs, ideally the same major versions of PHP, Node.js, Python or the database. If you are starting fresh, these guides cover the usual setup: first steps on a new server, Nginx and HTTPS, and Docker if your site runs in containers. If it does, copying the Compose file and the volumes is most of the move.
4. Copy the Files
Run rsync on the new server and pull from the old one. It needs SSH access to the old server (add the new server's key there, or use a password just for the move):
rsync -aHAX --numeric-ids --info=progress2 \ root@OLD_SERVER_IP:/var/www/ /var/www/
The trailing slashes matter: they copy the folder's contents into the folder of the same name. Copy the web server config and certificates the same way, then adjust anything that mentions the old IP address:
rsync -a root@OLD_SERVER_IP:/etc/nginx/sites-available/ /etc/nginx/sites-available/ rsync -a root@OLD_SERVER_IP:/etc/letsencrypt/ /etc/letsencrypt/
Copying /etc/letsencrypt whole keeps the certificates and their renewal settings, so HTTPS works before DNS points at the new server. Install certbot on the new server so renewals carry on, and enable each site (the symlinks in sites-enabled) once its paths are right.
5. Copy the Database
Create the database and its user on the new server first, with the same names and password as the old one so the app's config needs no change. Then stream a dump straight across. MySQL and MariaDB:
ssh root@OLD_SERVER_IP "mysqldump --single-transaction --routines --triggers --events mydb" \ | mysql mydb
PostgreSQL:
ssh root@OLD_SERVER_IP "sudo -u postgres pg_dump -Fc mydb" > mydb.dump sudo -u postgres pg_restore --no-owner --role=myapp -d mydb < mydb.dump
--single-transaction gives a consistent copy of InnoDB tables without locking the live site.
6. Test Before DNS Changes
Point only your own computer at the new server by adding a line to your hosts file (/etc/hosts on macOS and Linux, C:\Windows\System32\drivers\etc\hosts on Windows, edited as administrator):
NEW_SERVER_IP example.com www.example.com
Browse the site, sign in, submit a form, upload a file, and watch the logs on the new server while you do (sudo tail -f /var/log/nginx/error.log). Remove the line when you are done.
7. Switch Over
- Stop writes on the old server: put the site in maintenance mode or stop the app, so no new orders or comments land there.
- Run the rsync and database copy from steps 4 and 5 again. Only the changes are sent, so it is quick.
- Change the A record (and AAAA, if you use IPv6) for the domain and www to the new server's address.
- Start the app on the new server and watch its access log fill up as DNS changes over.
Recreate the cron jobs and background services from your list in step 1. They are the part most often forgotten.
8. Keep the Old Server for a Few Days
A few resolvers ignore short TTLs, so some traffic reaches the old server for a while. Leave it running (in maintenance mode) for a few days, and set up backups on the new server before you cancel the old one. How to back up a Linux server with Borg covers that.