How to move a Linux website to a new server with rsync
This guide shows how to move a Linux website, files and database included, to a new server with rsync, keeping downtime to a few minutes instead of hours. The core idea is a two-pass copy: a full sync while the old site keeps serving traffic, then a short write freeze, a fast delta sync and a DNS switch. It is written for anyone comfortable with SSH and a shell, and it works the same for a plain PHP site, WordPress or a custom application.
Why rsync runs twice
A single big copy has a built-in problem: it can take hours, and while it runs, visitors keep uploading files and writing to the database. If you switch DNS after one pass, everything created during the copy stays behind on the old server.
The two-pass pattern solves this. The first pass moves the bulk of the data while the site is fully online, so nobody notices it. The second pass runs after you freeze writes, and because rsync transfers only what changed since the first pass, it normally finishes in seconds to a few minutes. Your real downtime is the length of the second pass plus the database re-import, not the length of the full copy.
Prepare in advance: DNS TTL and the target server
Do these steps at least one day before the migration.
- Lower the DNS TTL. Set the TTL of the domain's A record to 300 seconds. Resolvers cache the old value for as long as the previous TTL allowed, so if the record was published with a 24 hour TTL, lower it at least 24 hours before the switch.
- Build the target server. Install the same web server, the same PHP or other runtime version, and the same database server as on the old machine. Version drift is the most common source of post-migration surprises.
- Recreate the vhost and TLS. Copy the site configuration and install the certificate. If you use certbot, you can rsync /etc/letsencrypt along with the site files.
- Set up SSH keys so the old server can reach the new one without a password:
ssh-keygen -t ed25519 ssh-copy-id root@NEW_SERVER_IP
First pass: copy everything while the old site runs
Run the bulk copy from the old server. The site stays online the whole time.
rsync -aHz --info=progress2 /var/www/example.com/ root@NEW_SERVER_IP:/var/www/example.com/
The flags: -a preserves permissions, ownership and timestamps, -H keeps hard links, -z compresses in transit. Mind the trailing slashes: they decide whether rsync copies the directory itself or its contents.
Dump the database with a consistent snapshot, which does not lock a site running on InnoDB tables:
mysqldump --single-transaction --routines --triggers example_db | gzip > /root/example_db.sql.gz rsync -z /root/example_db.sql.gz root@NEW_SERVER_IP:/root/
On the new server create the database and a user with the credentials the application expects, then import:
mysql -e "CREATE DATABASE example_db" zcat /root/example_db.sql.gz | mysql example_db
Also carry over the parts people forget: cron entries (crontab -l), custom php.ini values, and any systemd units the application depends on.
Test through your hosts file
Before touching DNS, browse the site on the new server exactly as a visitor would, virtual host, TLS and all. Add the new IP to the hosts file on your own workstation, not on the server:
# /etc/hosts (Windows: C:\Windows\System32\drivers\etc\hosts) NEW_SERVER_IP example.com www.example.com
Your browser now resolves the domain to the new server while the rest of the world still sees the old one. Click through the whole site: log in to the admin area, upload a file, submit a form, confirm HTTPS shows no certificate warnings. Watch the error log on the new server while you test:
tail -f /var/log/nginx/error.log
Remove the hosts entry afterwards so you always know which server you are looking at.
Switch day: freeze, delta pass, DNS
Go through this checklist before you start:
- The TTL has been at 300 seconds long enough for the old value to expire everywhere.
- The site works on the new server in the hosts file test.
- Cron jobs, outgoing mail and certificate renewal are set up on the new server.
- You know how to stop writes: application maintenance mode or stopping the app service.
- The old server stays untouched as your rollback path.
Then execute the switch:
- Freeze writes on the old server: enable maintenance mode in the application, or stop the runtime, for example
systemctl stop php8.2-fpm. Disable cron jobs that write data. - Run the delta pass. The
--deleteflag removes files on the target that were deleted on the source since the first pass:rsync -aHz --delete /var/www/example.com/ root@NEW_SERVER_IP:/var/www/example.com/
- Dump, transfer and import the database again with the same commands. Note that the dump is always full size: if the database is tens of gigabytes, plan the freeze window accordingly or set up replication instead.
- Change the A record of the domain to the new IP.
- Verify:
dig +short example.com @1.1.1.1should return the new IP, and the access log on the new server should start filling up. With a 300 second TTL most traffic moves within about 5 minutes.
For a typical site the whole frozen window, delta pass plus database import, is under 5 minutes.
Keep the old server as a rollback
Do not cancel the old server the same day. Keep it running for one or two weeks:
- If something serious surfaces on the new server, rollback is one DNS change back to the old IP, because the old site was frozen, not destroyed.
- Some resolvers ignore TTLs, so a trickle of visitors may hit the old IP for days. Watching its access log tells you when it goes quiet.
- It is your last copy of the data until the first proper backup cycle completes on the new server.
The same procedure covers a move between providers or an upgrade within one. If you are sizing the target machine, our VPS plans cover most sites, and when a site outgrows shared resources, a dedicated server gives it a machine of its own. Both run in our own Tier 3+ data centre in Riga, on our own network.