
September 10, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
You need to migrate a website between servers with zero data loss, but production cannot afford a bad rsync, a half-finished database dump, or a DNS flip that leaves uploads on the old box. On real client projects I have moved Laravel apps, WordPress shops, and legal portals between Ubuntu hosts using Deployer 7 and GitLab CI. The pattern is always the same: inventory everything, copy with verification, rehearse the cutover, then switch traffic only when checksums match. This guide walks through that workflow for PHP stacks on Linux system administration teams and solo operators alike.
How Do You Migrate a Website Between Servers with Zero Data Loss?
Zero data loss means every byte that mattered on the old server exists on the new one before you decommission anything. That includes code, uploaded media, cron jobs, SSL certificates, and every database row written up to the cutover window. Treat migration as a controlled release, not a Friday-night copy-paste.
The workflow has five phases: discovery, parallel sync, final delta, traffic switch, and validation. You never delete the source until the new host passes a full checklist and has run in production for at least 48 hours.
Start with a written runbook. List every path, cron entry, systemd unit, and environment variable. Sister sites I maintain on shared EC2 infrastructure share the same Deployer 7 pipeline, so the runbook also names the Git branch, deploy user, and PHP-FPM socket version.
- Document source server specs, OS version, web server, PHP version, and database engine.
- Provision the destination with matching or newer stack versions.
- Run an initial full sync while production stays live on the old host.
- Schedule a maintenance window for the final incremental sync and DNS change.
- Validate, monitor, and keep the old server powered on as a rollback target.
If you prefer hands-off delivery, website migration services cover the same checklist with staging rehearsal built in. The engineering steps remain identical whether you hire help or run them yourself.
What Should You Audit Before Moving Production Files and Databases?
Most data loss during server moves comes from forgotten paths, not failed commands. A Laravel app is never just /var/www/current. You also have storage/app, shared .env, Redis keys, and queue workers that write outside the release tree.
Build a complete asset inventory
Walk the filesystem and list every directory the application reads or writes. For WordPress 7.1 sites, that means wp-content/uploads, mu-plugins, and any custom tables plugins create. For Laravel 13 on PHP 8.3+, check storage/framework, bootstrap/cache, and Spatie Media Library disks if present.
# Source server — find large writable dirs
sudo du -sh /var/www/* /home/*/public_html 2>/dev/null
sudo crontab -l -u www-data
sudo systemctl list-units --type=service | grep -E 'php|queue|redis|mysql'
# List cron for root and deploy user
sudo crontab -l
crontab -l Capture configuration outside the repo
Virtual host files, Nginx snippets, PHP-FPM pools, and SSL paths live in /etc, not Git. Export them. On Apache setups I regularly use, the vhost and .htaccess rewrite rules must match or you lose pretty URLs overnight.
Database scope matters too. Note character set, collation, user grants, and whether you use MySQL 9.7 or PostgreSQL 18. Redis 8.10 session data may need a separate redis-cli --rdb dump if sessions must survive the move.
Record DNS and third-party integrations
Export DNS zone records or screenshot the registrar panel. Payment gateways like eSewa, Khalti, and Stripe often whitelist server IPs. Update webhook URLs only after the new host responds correctly. For SEO, read the SEO migration checklist for zero traffic loss before you touch canonical URLs or redirects.
Which Tools Safely Copy Website Data Between Linux Servers?
rsync over SSH is the default file transfer tool for Linux-to-Linux moves. It resumes interrupted transfers, preserves permissions, and supports checksum verification. For database bytes, logical dumps beat raw file copies because they handle version differences more gracefully.
File sync with rsync and verification
Run the first sync days before cutover. Exclude cache directories that regenerate on boot. Include user uploads and shared storage.
# On destination — create matching path
sudo mkdir -p /var/www/example.com
sudo chown -R deploy:www-data /var/www/example.com
# Initial sync from source (run on destination or source)
rsync -avz --progress \
--exclude 'storage/framework/cache/*' \
--exclude 'node_modules/' \
-e "ssh -p 22" \
deploy@OLD_SERVER:/var/www/example.com/ \
/var/www/example.com/
# Checksum verify — compare file lists
rsync -avnc --delete \
deploy@OLD_SERVER:/var/www/example.com/ \
/var/www/example.com/ | tee /tmp/rsync-dry-run.log The -n dry-run flag shows files that would still differ. Fix those before cutover. I compare this approach regularly with rclone in the rsync vs rclone backup comparison — rsync wins for same-OS server moves.
Database dump and restore
Take a consistent dump during low traffic. For MySQL, use mysqldump with single-transaction on InnoDB tables. For PostgreSQL 18, use pg_dump -Fc for compressed custom format restores.
# MySQL — on source
mysqldump -u root -p \
--single-transaction \
--routines --triggers \
--databases myapp_production \
| gzip > /backup/myapp_$(date +%F_%H%M).sql.gz
# Copy dump to destination
scp /backup/myapp_*.sql.gz deploy@NEW_SERVER:/backup/
# MySQL — on destination
gunzip -c /backup/myapp_*.sql.gz | mysql -u root -p
# PostgreSQL alternative
pg_dump -Fc -U appuser myapp_production > /backup/myapp.dump
pg_restore -U appuser -d myapp_production /backup/myapp.dump Record row counts before and after restore. A quick sanity query catches silent partial imports.
SELECT table_name, table_rows
FROM information_schema.tables
WHERE table_schema = 'myapp_production'
ORDER BY table_rows DESC; Validate JSON config files with a JSON formatter after editing .env or API payload fixtures. Typos there cause hours of false debugging.
Comparison of migration transfer methods
| Method | Best for | Verification | Risk if misused |
|---|---|---|---|
| rsync over SSH | Code, uploads, configs | --checksum or dry-run diff | Wrong excludes skip uploads |
| mysqldump / pg_dump | Relational databases | Row counts, spot queries | Non-transactional tables mid-write |
| redis-cli --rdb | Sessions, cache warm-up | Key count (DBSIZE) | Stale session data after long sync |
| Git + Deployer 7 | Laravel/PHP releases | Same commit hash deployed | Shared storage not in repo |
| Tar + scp | One-off small sites | Extract + md5sum | No resume on failure |
Always take a full backup before the first sync. The Ubuntu server backup strategies guide covers retention and off-site copies. Treat that backup as your insurance policy if the new server corrupts data during restore.
How Do You Cut Over DNS Without Downtime or SEO Loss?
DNS is the traffic switch. Lower TTL values 24 hours before migration so stale records expire quickly. Standard A-record TTL of 300 seconds gives you a five-minute rollback window if something breaks.
Rehearse on the new server first
Point a staging subdomain or edit your local /etc/hosts file to test the destination with the real domain name. Laravel needs APP_URL aligned, or signed URLs and password resets fail silently.
# Local test — map domain to new IP
echo "203.0.113.50 example.com" | sudo tee -a /etc/hosts
# Laravel post-restore
cd /var/www/example.com/current
php artisan config:cache
php artisan route:cache
php artisan migrate --force
php artisan queue:restart Install SSL on the new host before DNS changes. Certbot from Let's Encrypt can issue certificates once the domain resolves to the new IP, or use DNS-01 validation if HTTP-01 is not yet possible.
Execute the maintenance window
Put the old site in maintenance mode to stop new writes. Take the final database dump and rsync delta. Restore the dump on the destination. Run validation queries. Then update the A record at the registrar or domain and hosting panel.
- Enable maintenance mode on the source application.
- Run final
mysqldumpand incremental rsync. - Restore database on the destination and verify row counts.
- Smoke-test via hosts file or staging URL.
- Update DNS A record to the new server IP.
- Disable maintenance mode on the destination only.
- Monitor error logs for 30 minutes minimum.
For WooCommerce 11.1 shops, pause new orders during the final sync. Cart sessions stored in the database must be included in that last dump. I've seen checkout failures when operators synced files but missed the final transactional tables.
On legal-tech portals like Notary Kathmandu, document uploads land in dated folders outside Git. Those paths must appear in every rsync pass, not just the first one.
What Post-Migration Checks Confirm Nothing Was Lost?
Validation is where you prove zero data loss. Do not trust a green deploy badge alone. Walk through user-facing flows and compare machine-readable metrics.
Automated integrity checks
# File count comparison (run on both servers)
find /var/www/example.com/storage/app -type f | wc -l
# Checksum sample — large upload dirs
find /var/www/example.com/storage/app/public -type f -exec md5sum {} \; \
| sort > /tmp/new-md5.txt
# Compare with same command output from old server
# HTTP smoke test
curl -I https://example.com/
curl -I https://example.com/login
curl -s -o /dev/null -w "%{http_code}" https://example.com/api/health Check queue workers and cron on the new box. A common post-migration failure is a stale cron path still pointing at the old release directory. Re-read automated server backup setup and configure fresh nightly dumps on the destination immediately.
Application-specific validation
Laravel: confirm storage:link, run php artisan about, and test file uploads through the UI. WordPress: re-save permalinks, verify wp-content/uploads year folders, and run a plugin health check. Redis: compare DBSIZE if you migrated sessions.
Enable monitoring before you announce success. Netdata or Nagios alerts catch PHP-FPM exhaustion early. Read the Ubuntu server setup guide for baseline hardening on the new host.
Security hardening belongs in the same window. Apply UFW rules, fail2ban, and SSH key-only auth per the Ubuntu server hardening guide. A fresh server with old data is still a fresh attack surface.
Keep the old server online for seven to fourteen days. Snapshot it before shutdown. On Court Marriage In Nepal and similar Laravel properties, that rollback window saved a client when a payment webhook still pointed at the previous IP.
Ongoing support and maintenance should include post-migration monitoring for slow queries and 404 spikes. Technical SEO checks from search engine optimization services catch redirect chains early.
For Ansible-driven provisioning of the destination, see Ansible playbooks for PHP server provisioning. Matching PHP 8.3 or 8.5 builds across old and new hosts prevents Composer platform requirement failures.
The official MySQL mysqldump documentation explains transaction flags that keep InnoDB dumps consistent. Read it before you dump high-write tables during peak hours.
Key Takeaways
- Inventory every writable path, cron job, and integration before the first rsync — uploads and
.envfiles cause most silent data loss. - Run multiple rsync passes plus a final dump during a maintenance window with writes paused on the source.
- Verify with row counts, md5sum samples, and HTTP smoke tests — not gut feeling.
- Lower DNS TTL 24 hours ahead and keep the old server powered on for at least seven days as rollback insurance.
- Reconfigure backups, monitoring, SSL, and queue workers on the destination before you call the migration done.
- Document the runbook so the next developer can repeat the process without guessing paths.
People Also Ask
How long does a website server migration take?
A small WordPress site may finish in four to six hours including DNS propagation. Large Laravel apps with hundreds of gigabytes of uploads need one to three days of background rsync before a one-hour cutover window. Plan for DNS TTL reduction the day before.
Can you migrate without downtime?
Near-zero downtime is achievable with a final delta sync while the old site stays live, then a brief maintenance window of five to fifteen minutes for the last database dump. True zero-downtime requires read replicas or load-balancer draining, which is overkill for most SME sites in Nepal.
Should you upgrade PHP or MySQL during migration?
Migrate like-for-like first, validate, then upgrade on the new server in a separate step. Changing PHP 8.2 to 8.5 and MySQL 8.4 to 9.7 during the same move doubles your failure surface. Laravel 13 needs PHP 8.3 minimum — confirm compatibility before you bump versions.
What is the biggest cause of data loss during server moves?
Skipped user-upload directories and stale final database dumps top the list. Operators sync Git-tracked code but forget storage/app, wp-content/uploads, or media library disks. Always compare file counts and table rows between source and destination.
Ship Your Migration With a Rollback Plan Ready
You can migrate a website between servers with zero data loss when you treat files, databases, and DNS as one atomic operation. Audit first, sync with verification, rehearse on staging, cut over in a defined window, and validate before you decommission the old box. That workflow has kept production Laravel and WordPress sites stable across every move I have run since 2010.
Need a migration run without risking orders, leads, or legal documents? Review the website migration service or browse the portfolio for examples of live properties moved on Deployer 7 pipelines. For a scoped quote on your stack, reach out via contact us.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

