Migrate a website to a new VPS with zero downtime

The first time I migrated a production website, I did it the wrong way: copy files, dump the database, point DNS, and pray. The result? Fifteen minutes of downtime, broken images in the browser cache, and a night spent explaining to a client why their e‑commerce store took a nap at 2 PM. Years later, I realized that zero-downtime migration is not magic, it is a sequence of deliberate, reversible steps. This guide walks through a proven method to move a website from one Linux VPS to another, without a single lost request, using tools you already have: rsync, SSH, and a careful DNS cutoff.
- Key takeaway 1: Pre-seed the new server with a full rsync before the cutover window, this shrinks the final sync from hours to minutes.
- Key takeaway 2: Lower DNS TTL to 60 seconds at least 48 hours before the move, so the switch propagates fast.
- Key takeaway 3: Keep both servers running identically during the transition; flip the load balancer or DNS only after the new server handles traffic.
Prerequisites
- A source VPS (old server) running your website, any stack (Nginx/Apache + PHP/Node/Python + MySQL/PostgreSQL).
- A destination VPS (new server) with the same OS and software versions, Ubuntu 24.04 LTS and Debian 12 are solid choices. Linux VPS offers full root access for both.
- Root or sudo access on both machines.
- SSH key authentication set up between the two servers to enable passwordless rsync.
- DNS management access to your domain registrar or provider.
Why a naive migration breaks everything
A common mistake is to point DNS to the new IP, then copy the files. Visitors hitting the new server see a broken site (no database, stale assets), while others still on the old server see the old content. Database writes compound the problem, orders submitted after the copy are lost. Zero-downtime migration solves this by keeping both servers in sync up to the final flip, and using a load balancer or DNS together with a short TTL to drain the old server gradually.
Step 1, Prepare the new VPS with a base sync
Install the same web server and database engine on the new VPS. On the old server, take a snapshot or perform a dry run rsync to the new server so the bulk data is already there when you need it. Assuming your web root is /var/www/example.com and the DB is MySQL:
# On old server, sync web files to new server
rsync -avz --delete /var/www/example.com/ root@NEW_SERVER_IP:/var/www/example.com/
For MySQL or MariaDB, dump the database and pipe it directly into the new server:
# On old server
mysqldump --single-transaction --routines --triggers --databases exampledb \
| ssh root@NEW_SERVER_IP "mysql -u root -p"
Verify the new server serves your site locally (not yet on the public DNS) by editing your local /etc/hosts file and hitting it with curl. If the site works, the base sync succeeded.
Verify: run curl -I http://NEW_SERVER_IP, you should see a 200 response with your site content.
Step 2, Lower DNS TTL and keep the database in sync
DNS TTL (Time To Live) controls how long resolvers cache your domain's IP. Crank it down to 60 seconds at least 48 hours before the move, so when you flip, the change propagates in minutes, not hours.
From this point, set up a continuous sync loop for the database and a final rsync for the files. For MySQL, configure the new server as a replication replica of the old one, or use an automated dump-and-restore cron every minute during the transition window. The simplest approach for a short window is a two‑step rsync:
# Sync files one last time during the cutover window
rsync -avz --delete /var/www/example.com/ root@NEW_SERVER_IP:/var/www/example.com/
For the database, a --flush-logs dump followed by an immediate restore ensures the new DB is within seconds of the old one.
Note: If your site accepts writes, replication is safer. A read-only maintenance page for 30 seconds is sometimes acceptable, but a true zero-downtime migration requires replication.
Step 3, Stage the cutover with a local Nginx reverse proxy (optional but clean)
If you control both servers, use a third machine (or the old server itself) as a temporary reverse proxy. Point DNS to the proxy. The proxy forwards traffic to the old server until you decide to flip, then it redirects to the new server, no DNS propagation delay. Example Nginx config on the proxy:
upstream backend {
server OLD_SERVER_IP:80; # old server active
# server NEW_SERVER_IP:80; # comment this until flip
}
server {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://backend;
}
}
On cutover, uncomment the new server line and comment the old one, then nginx -s reload. The switch is instant.
Verify: run curl -H "Host: example.com" http://PROXY_IP, the response comes from the server you selected.
Step 4, Flip DNS (or the proxy) and test
If you used the proxy approach, the flip is already done. If not, update the A/AAAA records on your DNS provider to point to the new VPS IP. Because the TTL was lowered, most users will resolve the new IP within a minute.
At this stage, run a comprehensive smoke test on the new server:
- Load the home page and internal pages,
curl -sI https://example.com - Submit a test form or perform a test checkout.
- Check logs on the new server:
tail -f /var/log/nginx/access.log - Verify the database is current by comparing row counts on a recent table.
Step 5, Drain old server and decommission
After you confirm the new server is serving all traffic cleanly for 24 to 48 hours, update the old server's firewall to drop all incoming HTTP/HTTPS:
# On old server
ufw deny http
ufw deny https
Monitor for any traffic hitting the old server (check its access logs). After 48 hours with no requests, you can safely decommission the old VPS. Take a final backup of the old server before shutting it down.
FAQ
How do I migrate a database without downtime?
Use MySQL replication: set the new server as a replica of the old one, let it catch up, then promote it to master. For a shorter window, a mysqldump with --single-transaction piped over SSH works if you can accept a few seconds of lag during the final sync.
What if my DNS provider doesn't support low TTL?
Use Cloudflare or a similar reverse proxy between your domain and both servers. Cloudflare can point to the old origin, then you switch the origin IP inside the dashboard, instant change, no DNS reliance.
Do I need to migrate SSL certificates?
Yes. Copy your certificate files to the new server (usually under /etc/ssl or /etc/letsencrypt) and ensure the Nginx/Apache config references them. If you use Certbot, run certbot renew --dry-run on the new server first.
Can I migrate a WordPress site this way?
Yes. WordPress migration is files + database. The steps above apply directly. Also copy the wp-config.php file and ensure the database credentials match. After the move, update the Site URL in the wp_options table if needed.
How long does a zero-downtime migration take?
The initial rsync can take hours for large sites. The actual cutover (final sync + DNS flip or proxy reload) takes 1-5 minutes. Users notice nothing if the proxy approach is used.
Related articles
- Zero-downtime deploy with Nginx and systemd on Ubuntu 24.04
- Configure DNS records for your domain: A, CNAME, MX, TXT
- Reverse proxy with automatic HTTPS using Caddy


