Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Migrate a Website Between Servers with Zero Data Loss

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.

Website Server Migration Phases1. AuditInventory assets2. Syncrsync + DB dump3. RehearseStaging test4. CutoverDNS + final sync5. Validate — row counts, hashes, logs, SSLKeep old server 7+ days as rollbackGoal: identical files + DB on new host before DNS points away from old server
Five-phase plan to migrate a website between servers with zero data loss — audit, sync, rehearse, cutover, validate

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.

  1. Document source server specs, OS version, web server, PHP version, and database engine.
  2. Provision the destination with matching or newer stack versions.
  3. Run an initial full sync while production stays live on the old host.
  4. Schedule a maintenance window for the final incremental sync and DNS change.
  5. 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.

Pre-Migration Audit ChecklistApplication Layer• Code + vendor (Composer)• storage/ uploads & media• .env secrets & APP_KEY• queue workers & schedulersInfrastructure Layer• vhost + SSL cert paths• PHP-FPM pool version• cron + systemd units• firewall + fail2ban rulesData Layer• MySQL / PostgreSQL dump• Redis sessions & cache• Row counts per table• DB user grantsExternal Layer• DNS A/AAAA/CNAME/MX• Payment IP whitelists• SMTP / SMS credentials• CDN origin settings
Audit four layers — application, infrastructure, data, and external integrations — before any server migration

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

MethodBest forVerificationRisk if misused
rsync over SSHCode, uploads, configs--checksum or dry-run diffWrong excludes skip uploads
mysqldump / pg_dumpRelational databasesRow counts, spot queriesNon-transactional tables mid-write
redis-cli --rdbSessions, cache warm-upKey count (DBSIZE)Stale session data after long sync
Git + Deployer 7Laravel/PHP releasesSame commit hash deployedShared storage not in repo
Tar + scpOne-off small sitesExtract + md5sumNo 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.

Verified Data Transfer PipelineOld Server/var/www filesMySQL / PostgresRedis RDB/etc configsrsync -avzSSH port 22mysqldumpgzip + scpNew ServerMatching pathsRestored DBRow count matchmd5sum verifyAbort if checksums differ
rsync and database dumps flow from the old server to the new server with checksum validation before cutover

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.

  1. Enable maintenance mode on the source application.
  2. Run final mysqldump and incremental rsync.
  3. Restore database on the destination and verify row counts.
  4. Smoke-test via hosts file or staging URL.
  5. Update DNS A record to the new server IP.
  6. Disable maintenance mode on the destination only.
  7. 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.

Post-Migration Validation TreeDNS points to new IPRow counts match?NORollback DNSYESUploads + SSL OK?Cron + queues running?Sign off — keep old 7 days
Post-migration validation decision tree — rollback DNS if row counts or uploads fail verification

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 .env files 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

Every production byte that mattered on the old server—code, uploaded media, cron jobs, SSL certificates, and database rows through the cutover window—must exist on the new host before you decommission anything.

Small WordPress sites often 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 plus a one-hour cutover window.

Near-zero downtime is achievable with a final delta sync while the old site stays live, then a five-to-fifteen-minute maintenance window for the last database dump. True zero-downtime needs read replicas, which is overkill for most SME sites.

Most data loss comes from forgotten paths, not failed commands. Walk every writable directory: Laravel needs storage/app, shared .env, and queue worker output; WordPress 7.1 needs wp-content/uploads and mu-plugins. Export vhosts, PHP-FPM pools, and SSL configs from /etc. List cron jobs for www-data, root, and deploy users. Record DNS records, payment gateway IP whitelists, database charset and collation, and whether Redis 8.10 session data must survive the move.

rsync over SSH is the default for same-OS Linux moves—it resumes interrupted transfers, preserves permissions, and supports checksum verification. mysqldump or pg_dump handle relational databases with row-count checks afterward. redis-cli --rdb covers session data if needed. Git plus Deployer 7 deploys Laravel release code but misses shared storage outside the repo. Always take a full backup before the first sync as insurance if the restore corrupts data.

Run multiple rsync passes days before cutover, excluding only regenerable cache like storage/framework/cache. Then run rsync -avnc --delete for a dry-run diff showing files that still differ. Compare upload directory file counts with find and wc -l on both servers. Sample large upload folders with sorted md5sum lists from old and new hosts. Fix every mismatch before pausing writes for the final delta sync.

On the source, run mysqldump with --single-transaction, --routines, and --triggers during low traffic, then gzip the output. Copy the archive via scp and restore on the destination with gunzip piped into mysql. Query information_schema.tables for table_rows before and after restore to catch silent partial imports. Non-transactional tables need writes paused during the final dump taken inside the maintenance window.

Lower TTL to 300 seconds twenty-four hours ahead so stale records expire quickly. Rehearse on the new server via /etc/hosts or a staging subdomain with APP_URL aligned, or signed URLs and password resets fail silently. Install SSL with Certbot before updating DNS. In the maintenance window: enable maintenance mode, take the final dump and rsync delta, restore and verify, smoke-test, update the A record, disable maintenance on the destination only, and monitor error logs for at least thirty minutes.

Compare upload directory file counts and md5sum samples between servers. Verify database row counts match pre-restore figures. Run HTTP smoke tests on homepage, login, and API health endpoints with curl. Laravel: confirm storage:link, run php artisan about, and test file uploads through the UI. WordPress: re-save permalinks and verify wp-content/uploads year folders. Check queue workers and cron paths point to the new release directory, not the old host.

Migrate like-for-like first, validate production behavior on the new host, then upgrade 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 Composer platform requirements and package compatibility before any version bump on the destination server.

Skipped user-upload directories and stale final database dumps top the list. Operators rsync Git-tracked code but forget storage/app, wp-content/uploads, Spatie Media Library disks, or dated upload folders outside the repo. On WooCommerce 11.1 shops, syncing files but missing the final transactional dump causes checkout failures. Always compare file counts and table rows between source and destination before calling the migration done.

A Laravel app is never just /var/www/current. Inventory storage/app, shared .env, bootstrap/cache, Redis keys, and queue workers writing outside the release tree. Run initial rsync excluding framework cache while production stays live. On cutover, run php artisan config:cache, route:cache, migrate --force, and queue:restart. Use Deployer 7 and GitLab CI for code deploys, rsync for shared storage, and mysqldump with row-count verification for the database.

wp-content/uploads and mu-plugins live outside Git and get skipped when operators copy only core or theme files. Custom tables created by plugins must be included in the mysqldump scope. For WooCommerce 11.1 shops, pause new orders during the final sync so cart sessions stored in transactional tables are captured in the last dump. After restore, re-save permalinks or Apache rewrite rules that worked on the old host will break pretty URLs overnight.

Keep it powered on for seven to fourteen days as rollback insurance and snapshot it before shutdown. Do not delete the source until the new host passes a full checklist and has run in production for at least forty-eight hours. Payment webhooks for gateways like eSewa, Khalti, and Stripe may still point at the previous IP days after DNS changes, and a live old box lets you revert the A record within minutes if validation fails.

Discovery produces a written runbook listing every path, cron entry, systemd unit, environment variable, Git branch, deploy user, and PHP-FPM socket version. Parallel sync runs initial rsync and backup while production stays on the old host. Final delta happens in a maintenance window with writes paused on the source. Traffic switch updates DNS only after checksums and row counts match. Validation covers smoke tests, fresh backups and monitoring on the destination, UFW and fail2ban hardening, and keeping the old server as rollback until confidence is proven.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: