Zero-Downtime Website Migration: A Complete rsync and DNS TTL Playbook

There’s an old-school way to migrate a live site: pick a dead-quiet Tuesday at 3 a.m., slap up a maintenance page, and hope DNS propagates before anyone important notices. That still works fine for a hobby blog. For anything with paying customers or active sessions, it’s a needless gamble. Done properly, a zero downtime migration lets you move a production site — files, database, mail routing, cron jobs, all of it — without a single visitor noticing anything happened.
Copying files isn’t the hard part. Rsync does that job reliably and has for two decades. What actually trips people up is coordinating three clocks that don’t run in sync: when your data is genuinely caught up between servers, when DNS resolvers around the world actually pick up the new IP, and when it’s safe to finally walk away from the old box. Get that sequencing wrong and you’ll serve stale pages, drop form submissions into a void, or — worse — end up with two databases quietly diverging while traffic splits between them.
This guide walks through an rsync-based server migration paired with proper DNS TTL prep ahead of time. That combination is what actually makes a live website migration possible without ever hanging a “back in 20 minutes” sign on the door.
Pre-Migration Planning and Assessment
Auditing the Current Server Environment
Before you touch anything, inventory the source server top to bottom. PHP/Python/Node versions, installed extensions, every user’s cron jobs (not just root’s — run crontab -l per user), mail configuration, SSL certificate details, and any firewall rules that aren’t already staring at you from the vhost config. I’ve been burned by a “simple” WordPress migration once where a cron job was quietly syncing inventory from a third-party API — nobody remembered it existed until orders stopped updating three days later and someone asked why stock counts were frozen.
Check connection counts and write patterns on the database too. A high-write OLTP workload needs a very different cutover approach than a mostly-static brochure site.
Choosing the Destination Server and Matching Software Versions
Match major software versions on the new server before you move a single byte — same PHP minor version, same MySQL/MariaDB flavor, same web server, and if it’s Apache, the same module set. Version drift is probably the single most common reason migrations “work in staging” and then quietly break after cutover. If an upgrade is part of the plan, do it as its own project afterward and test it properly — don’t bundle a version jump with a server move. Change one variable at a time, always.
Creating a Migration Timeline and Rollback Plan
Write actual timestamps down, not just a vague list of steps. Something like: TTL dropped at T-48h, first full rsync at T-24h, incremental syncs every few hours after that, final sync plus DB dump at T-0, DNS cutover at T-0, source server frozen read-only until T+72h. And decide your rollback trigger in advance — “if error rate exceeds X% in the first hour, revert DNS” — before you’re mid-cutover and tempted to just wing it.
Lowering DNS TTL in Advance
Understanding TTL and Resolver Caching Behavior
Resolvers cache your A/AAAA records for as long as your TTL tells them to, and they couldn’t care less about your migration schedule. Plenty of ISP resolvers are also flat-out non-compliant and hold onto records longer than instructed — which is exactly why the overlap window after cutover isn’t optional, it’s insurance.
Setting TTL to 300 Seconds 24-48 Hours Ahead
Drop your TTL down to 300 seconds (5 minutes) at least 24-48 hours before cutover. That gives the old, longer TTL time to fully age out of caches everywhere before you actually need fast propagation to matter. Don’t lower the TTL and migrate the same day — you’ll still be fighting cached records left over from the previous, higher TTL. Verify the change has actually propagated with dig +trace yourdomain.com run from a couple of different networks, not just the office Wi-Fi.
Building and Syncing the Destination Server
Get everything that isn’t tied to an IP address configured well ahead of time — firewall rules, vhost configs, PHP-FPM pools, SSL certs. If you’re on Let’s Encrypt, you can often knock out DNS-01 or even HTTP-01 validation against a staging subdomain before cutover day, or just carry over the existing cert and key if it came from a commercial CA. The goal is that by cutover, the only things left to flip are IP-bound: DNS records, and maybe a few outbound whitelist entries on third-party services.
Kick off your initial full sync well in advance:
rsync -avz --delete /var/www/site/ user@newserver:/var/www/site/
-a preserves permissions, ownership, and timestamps; -z compresses data in transit; --delete keeps the destination clean instead of accumulating orphaned files as the source keeps changing underneath you. Run this early, then follow up with incremental syncs every few hours as cutover approaches — each pass only has to move a shrinking delta, which is what keeps your final pre-cutover sync short and boring.
Staging Validation Before DNS Ever Changes
Add a temporary hosts file entry on your workstation, or point a staging subdomain at the new server’s IP, and actually put the site through its paces there — database connectivity, file upload permissions, cron execution, outbound mail, session handling. This is where you find the missing PHP extension or the config file with a hardcoded IP address baked into it, while it’s still nobody’s emergency.
The Cutover Sequence
When the moment arrives, order matters — don’t improvise this part:
- Flip the application into brief read-only or maintenance mode on the source — seconds, not hours, just long enough to cover the write-sensitive final steps.
- Take a final database export with
mysqldump --single-transaction --routines --triggers dbname > final.sql, which gets you a consistent snapshot without locking tables on an InnoDB database. - Import that dump on the destination, then run one last rsync pass with checksum verification:
rsync -avz --checksum --delete /var/www/site/ user@newserver:/var/www/site/
--checksum matters here because timestamp comparisons alone can lie after multiple incremental syncs across different filesystems — this gives you byte-for-byte certainty instead of a guess.
- Update DNS to point at the new server’s IP.
- Take the source out of maintenance mode, but leave it running, fully intact, untouched.
Post-Cutover: Monitoring and the Safety Window
Leave the source server running, unchanged, for 48-72 hours minimum. This isn’t foot-dragging — it’s your rollback insurance and your buffer against resolvers that ignore TTLs entirely. Tail the access logs on both servers side by side:
tail -f /var/log/nginx/access.log
Watch request volume on the old server taper off toward zero. Some poorly-behaved ISP resolvers will keep dribbling a bit of traffic its way for days. Don’t decommission it until that trickle has genuinely stopped — not just slowed down.
Best Practices
- Never lower TTL and migrate the same day — let the old TTL expire naturally first.
- Script your incremental rsync passes with logging, so you’ve got a clear audit trail of what changed and when.
- Pin software versions between source and destination before migrating; save upgrades for afterward, as a separate step.
- Test the staging subdomain with actual user workflows — checkout, login, file uploads — not just a homepage load.
- Freeze the source server’s filesystem and database read-only instead of shutting it down, so rollback is instant if you need it.
- Log every command you run during cutover in real time; it makes post-incident review far less painful.
- Confirm outbound mail (SPF/DKIM) and any IP-based API whitelists are updated for the new server before you decommission the old one.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| Some visitors see the old site for days | TTL wasn’t lowered far enough in advance, or an ISP resolver is ignoring TTL | Confirm TTL propagation before migrating next time; keep monitoring old server logs and let the overlap window run its course |
| Site loads but database errors appear | Final mysqldump/import happened before the last write batch, or credentials weren’t updated | Re-run the single-transaction dump/import and confirm config files point to the local DB host |
| Uploaded files missing after cutover | Final rsync pass ran before the last user upload, or a directory got excluded | Re-run rsync with –checksum against the source before fully retiring it |
| SSL warnings on new server | Certificate wasn’t transferred, or Let’s Encrypt renewal is still tied to old IP validation | Reissue or re-validate the cert against the new server ahead of cutover, not during it |
| Cron-driven tasks not running post-migration | Crontabs weren’t audited or copied for every user | Diff crontab -l output between servers for every user account |
Conclusion
Zero downtime migration isn’t one clever trick — it’s disciplined sequencing: DNS TTL prep done early, incremental rsync passes chipping away at the delta, and a conservative overlap period where the old server stays alive purely as insurance. Handle the boring parts well ahead of time — lower the TTL days out, match software versions, validate on staging before DNS ever moves. Then handle the risky parts fast and precisely — the checksum-verified final sync, the single-transaction dump, the cutover itself. Do it this way and a live website migration turns into routine maintenance instead of a white-knuckle gamble, and your rollback plan stays exactly what it’s supposed to be — insurance you hope you never actually need to cash in.
Frequently Asked Questions
Why is lowering the DNS TTL important before migration?
Resolvers around the world cache your A/AAAA records for however long your TTL says to. Dropping the TTL to 300 seconds well ahead of cutover means that once you switch IPs, most resolvers pick up the change within minutes instead of serving the old server’s address for hours or days. That shrinks the window where some fraction of visitors gets sent to the wrong place.
How does rsync help achieve zero downtime during migration?
Rsync lets you do a full initial copy while the source site is still live and serving traffic normally, then run incremental syncs afterward that only move what’s changed. That shrinks the final sync window down to seconds or minutes instead of needing to copy the entire site during the actual cutover. Flags like -avz –delete keep things clean along the way, and a final –checksum pass confirms the destination is a byte-for-byte match before you redirect any traffic.
How do I handle the database without losing data during cutover?
Best option, if it fits your setup, is continuous replication so the destination database is never more than a few seconds behind. Otherwise, a final mysqldump with –single-transaction run immediately before cutover gets you a consistent snapshot without locking tables on a live site. For high-write applications, setting up master-slave replication ahead of time removes the need for a last-minute dump altogether.
Should I keep the old server running after migration?
Yes — keep the source server running unchanged, ideally read-only, for 48 to 72 hours after cutover. It’s your instant rollback option, and it covers you for visitors whose ISP resolvers didn’t respect the lowered TTL like they were supposed to. Only decommission it once the access logs confirm traffic has genuinely and fully shifted to the new server.
How can I test the new server before actually switching DNS?
Add a temporary hosts file entry on your own machine, or stand up a staging subdomain pointed straight at the new server’s IP. That lets you check database connectivity, file permissions, cron jobs, and SSL setup in a production-like environment without any real visitors involved. Thorough testing here is what catches a configuration mismatch before it becomes a customer-facing problem instead of after.
What should be configured on the destination server in advance?
Firewall rules, SSL certificates (via Let’s Encrypt or a transferred cert), and web server vhost configs should all be sorted and verified before migration day arrives. Get that out of the way early and cutover day is left with just IP-dependent items and the final data sync — which keeps the whole window short and the risk of last-minute surprises low.
Learn more article: What steps can I take to make the most of Ownwebservers cloud web hosting?