Most bad migrations are not caused by the file copy. They are caused by assumptions around DNS, email, forms, caching, and rollback. A checklist turns a tense afternoon into a predictable process.

Phase 1: Inventory before you touch anything

DNS records

dig ANY example.com
dig TXT example.com | grep -i spf
dig TXT _dmarc.example.com
dig MX example.com
dig NS example.com

Export the full zone from your DNS provider. Save it somewhere outside the server you are migrating.

Email hosting

Confirm where email actually lives:

  • Check MX records — they may point to Google Workspace, Microsoft 365, or a separate mail provider
  • Confirm whether email is tied to the hosting account
  • List all email accounts, aliases, forwarders, and shared mailboxes
  • Check SMTP credentials used by WordPress (FluentSMTP, Post SMTP, WP Mail SMTP)

Email is the most common casualty of a migration that treats web and mail as one thing. They are not.

Forms and integrations

List every form on the site and where it delivers:

  • Contact forms → which email address?
  • Newsletter signup → which service (Mailchimp, ConvertKit, etc.)?
  • WooCommerce order notifications → where do they go?
  • Booking/calendar → which external service?
  • CRM → HubSpot, Salesforce, or custom?
  • Payment gateway → Stripe, PayPal, WooPayments?

Test each integration on the new server before DNS cutover.

Current stack snapshot

php -v
wp core version
mysql --version
wp plugin list --format=json | jq '.[] | {name, version}' > plugin-versions.json
wp theme list --format=json > theme-versions.json

Record the current PHP version, MySQL/MariaDB version, and server OS. The new server needs to match or exceed these, and downgrading PHP versions is a common source of fatal errors.

File inventory

du -sh wp-content/uploads
du -sh wp-content/plugins
du -sh wp-content/themes

Large uploads directories may need a staged transfer. Sites with more than 5 GB of uploads often benefit from rsync with delta transfers rather than a full copy.

Phase 2: Backup and rollback preparation

Full backup

# Database
mysqldump --single-transaction --routines --triggers database_name | gzip > backup-$(date +%Y%m%d).sql.gz

# Files
tar -czf files-$(date +%Y%m%d).tar.gz --exclude='wp-content/cache' --exclude='wp-content/backups' .

Build the rollback plan

Before changing DNS, know exactly how to put traffic back:

  • Old server IP: ________
  • Old DNS provider login: accessible? Y/N
  • Old DNS zone export location: ________
  • Database export timestamp: ________
  • Old hosting still paid up until: ________
  • Person who can approve DNS changes: ________

A rollback that takes 30 minutes is acceptable. A rollback that takes four hours because someone needs to find DNS credentials is not.

Phase 3: Lower DNS TTLs

Lower TTLs on important records at least 4 hours before the migration, preferably 24:

  • A/AAAA records for the domain and www
  • MX records (if changing)
  • TXT records for SPF, DKIM, DMARC
# Verify TTL is lowered across resolvers
dig @1.1.1.1 A example.com | grep -E '^[a-z].*[0-9]+$'
dig @8.8.8.8 A example.com

Check propagation:

dig +short A example.com @1.1.1.1
dig +short A example.com @8.8.8.8
dig +short A example.com @9.9.9.9

Phase 4: Test the new server thoroughly

Use a hosts file override to test before DNS cutover:

# /etc/hosts
203.0.113.50 example.com www.example.com

Or use a staging domain. Test every function:

Frontend

  • Homepage
  • Key landing pages
  • Blog posts
  • Search
  • Category and tag archives
  • Custom post type archives

Admin

  • Login
  • Dashboard
  • Post editor (Gutenberg and Classic)
  • Media library
  • Plugin activation/deactivation
  • Theme customizer

Forms

  • Contact form submission
  • Newsletter signup
  • Checkout (test mode)
  • Password reset email
  • WooCommerce order notification (test order)

Performance and caching

  • Cache headers present
  • CDN serving assets
  • Redis/Memcached connected (if used)
  • SSL certificate valid
  • Redirects working (www → non-www or vice versa)

Security

  • SSL certificate covers the domain
  • Security headers present
  • XML-RPC handled appropriately
  • wp-admin accessible only as expected

Phase 5: Cut over DNS

  1. Update A/AAAA records to point to the new server IP
  2. Update NS records if changing nameservers
  3. Verify with dig across multiple resolvers
  4. Wait for TTL to expire (check propagation tools like whatsMyDNS.net)

Phase 6: Post-migration verification

Immediately after DNS is pointing to the new server:

  • Site loads from multiple locations (VPN, mobile data)
  • SSL certificate is valid
  • Submit test contact form
  • Test email delivery (check spam folder)
  • Verify WooCommerce/Stripe webhooks still fire
  • Check Google Search Console for crawl errors
  • Monitor error logs for the first hour
  • Keep old server running for 72 hours minimum

Phase 7: Clean up after confirmation

After 72 hours with no issues:

  • Final audit of old server logs for missed traffic
  • Cancel old hosting (confirm no DNS records still point to it)
  • Remove hosts file overrides
  • Raise DNS TTLs back to normal (600–3600 seconds)
  • Document the migration in client records
  • Verify backups are running on the new server

A migration is not complete when the site loads. It is complete when backups run, monitoring is active, and the old server is decommissioned.