
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Server migrations fail when teams treat them as simple file copies rather than state synchronization problems. To migrate a website between servers with zero data loss, you must synchronize the filesystem, freeze database writes during the final cutover, and manage DNS propagation explicitly. This disciplined approach prevents the orphaned records and missing assets that plague ad-hoc transfers. If you are moving from shared hosting or an outdated VPS, reviewing migration strategies for cloud transitions provides essential context before executing these steps.
How do you prepare a server environment before migrating a website?
Most data loss occurs because the destination environment differs subtly from the source. Before transferring a single byte, you must replicate the exact runtime configuration. This includes PHP versions, Nginx/Apache modules, system libraries, and user permissions. A mismatched php.ini or missing ImageMagick library won't stop the transfer, but it will corrupt functionality post-cutover.
Audit and replicate source configuration
- Capture installed packages: On Debian/Ubuntu source servers, run
dpkg --get-selections > packages.txt. On RHEL/CentOS, useyum list installed > packages.txt. - Export PHP/runtime config: Copy
/etc/php/8.3/fpm/php.ini, pool configs, and any custom extensions. Do not rely on defaults. - Document cron jobs and services: Run
crontab -lfor every application user. Export systemd unit files if you use custom services. - Verify disk and inode capacity: Ensure the destination has at least 20% more space than current usage. Check inodes with
df -i; many migrations fail because files fit but inodes exhaust.
For teams adopting infrastructure-as-code, defining this environment in Terraform or Ansible eliminates drift. My practical Terraform guide covers provisioning identical staging environments that serve as safe migration targets.
What is the safest method to transfer files without corruption?
rsync remains the industry standard for file transfer because it supports delta synchronization, permission preservation, and verification. Avoid scp or FTP for production migrations; they lack resume capability and integrity checks. The critical flags are -avzHAXS: archive mode, verbose, compression, hard-links, ACLs, extended attributes, and sparse file handling.
# Initial bulk transfer (run days before cutover)
rsync -avzHAXS --progress \
--exclude='.env' \
--exclude='storage/logs/*' \
--exclude='node_modules/' \
/var/www/site/ user@dest-server:/var/www/site/
# Verify integrity after transfer
ssh user@dest-server "find /var/www/site -type f -exec md5sum {} \; > /tmp/dest_checksums.txt"
find /var/www/site -type f -exec md5sum {} \; > /tmp/source_checksums.txt
diff /tmp/source_checksums.txt /tmp/dest_checksums.txt The --exclude flags prevent overwriting environment-specific configurations and transient logs. Always exclude .env files; these contain credentials unique to each server. Generate checksums on both sides and diff them. If any hash mismatches, re-run rsync for those specific paths. This verification step is non-negotiable for claiming zero data loss.
How do you migrate databases with full transactional consistency?
File transfers are forgiving; database migrations are not. Copying raw MySQL/MariaDB data directories while the service runs guarantees corruption. You must use logical dumps with proper locking or physical backups from stopped replicas. For PostgreSQL, use pg_dump with --format=custom for parallel restore capability.
MySQL/MariaDB safe export procedure
- Enable maintenance mode in your application to halt new writes. For Laravel, run
php artisan down --retry=60. - Dump with transactional consistency:
Themysqldump --single-transaction --routines --triggers \ --set-gtid-purged=OFF --quick \ -u root -p production_db > /backups/prod_$(date +%F).sql--single-transactionflag ensures InnoDB tables dump consistently without global locks. Add--master-data=2only if setting up replication. - Compress and transfer: Pipe through
gzipbefore rsync to reduce transfer time by 70–90%. - Restore and validate: After importing, compare row counts per table between source and destination. Use
SELECT COUNT(*) FROM table_name;on both servers. Matching counts don't guarantee correctness, but mismatches guarantee failure.
If your application cannot tolerate even brief write freezes, configure read replicas and promote them on the destination. This adds complexity but enables true zero-downtime database migration. For most SMB workloads I manage in Nepal and globally, a planned 5–10 minute maintenance window with proper communication outperforms complex replication setups.
How should you manage DNS TTLs to prevent traffic loss during cutover?
DNS propagation is where "zero data loss" migrations silently fail. Users hitting the old server after you've frozen writes creates lost orders, failed logins, and corrupted sessions. Lower your DNS TTL to 300 seconds (5 minutes) at least 48 hours before migration. This ensures resolvers refresh frequently during your cutover window.
| TTL Value | Propagation Time | Use Case | Risk Level |
|---|---|---|---|
| 86400 (24h) | Up to 24 hours | Stable production (default) | High during migration |
| 3600 (1h) | Up to 1 hour | Pre-migration (1 week prior) | Moderate |
| 300 (5m) | 5–10 minutes | Cutover window | Low (recommended) |
| 60 (1m) | 1–2 minutes | Emergency rollback | Higher query load |
After updating the A/AAAA record to the destination IP, monitor DNS resolution globally using tools like dig @8.8.8.8 yourdomain.com or public DNS checkers. Keep the source server running and accepting traffic for at least 2× your previous TTL duration. Only decommission the source after confirming zero requests in access logs for that period. If you're also implementing SSL on the new server, follow Let's Encrypt setup best practices to avoid certificate warnings during the transition.
What validation checklist confirms a successful migration with zero data loss?
Assuming success because the site loads is professional negligence. Systematic validation catches silent failures before users do. Create a written checklist covering functional, data, and performance dimensions.
Critical verification steps
- Checksum parity: Every file transferred must have identical MD5/SHA256 hashes. Automate this; manual spot-checks miss edge cases.
- Database row counts: Script comparisons for all tables. Investigate any discrepancy immediately—don't assume it's "just cache."
- Functional smoke tests: Execute core user journeys (login, checkout, form submission) on the destination via its IP address using Host header overrides before DNS switches.
- Performance baselines: Compare p95 response times. A 3× slowdown indicates misconfigured caching, missing indexes, or resource constraints.
- Outbound connectivity: Test email sending, API webhooks, and third-party integrations. New servers often have different egress firewall rules or SMTP restrictions.
- Scheduled tasks: Confirm crons executed at expected times. Check logs, not just configuration files.
Document every validation result. For compliance-focused environments (SOC 2, ISO 27001), this evidence demonstrates controlled change management. Teams deploying Laravel applications should pair this with zero-downtime deployment tooling to automate future releases post-migration.
Migrate a Website Between Servers with Zero Data Loss: Final Checklist
Successful migration isn't about speed—it's about predictable outcomes. Lower DNS TTLs early, verify every transfer with checksums, freeze writes during database exports, and validate systematically before accepting traffic. The extra hours spent on preparation save days of incident response. If your team lacks bandwidth for rigorous validation or needs audit-ready documentation for compliance frameworks, reach out to discuss your migration requirements. Production systems deserve methodical execution, not hopeful guessing.