Cloud & Infrastructure
Cloud migration checklist: the 12 things that break most migrations
TL;DR
- Most failed migrations don't fail on the big architectural decisions, they fail on network rules, IAM, DNS timing, and licensing assumptions nobody double-checked.
- Split the checklist into three phases, before, during, and after cutover, each with its own risks, instead of one giant "migrate everything" task.
- Always have a tested rollback path. If you can't reverse the cutover, you're not actually ready for it.
- The migration isn't done when the app starts responding. It's done when monitoring, data integrity, and compliance are all confirmed.
Most migration plans focus on the big decision, lift-and-shift or re-architect, which cloud, which region. Those decisions matter, but they're rarely what actually breaks a migration. The things that break migrations are smaller and more specific: a firewall rule that didn't get copied, a DNS record with a 24-hour TTL nobody lowered in advance, a licensing model that behaves differently on the new platform. Here are the 12 that come up again and again, grouped by when you need to catch them.
Before you migrate
This is where most of the real risk gets created or removed. Skipping planning doesn't save time, it just moves the cost to the middle of cutover weekend.
Map every dependency
Cron jobs, internal services, shared databases, if it's not on the map, it gets missed at cutover.
Plan the data migration separately
Decide upfront: maintenance window, dual-write, or continuous replication with a short final sync.
Review network and security
Copying old firewall rules blindly carries over whatever was already too permissive.
Check licensing terms
Per-core and per-instance pricing can shift dramatically on a new platform. Check first.
A quick dependency scan on what looked like a "simple" three-service app is usually where the surprises show up:
$ grep -rl "DATABASE_URL|REDIS_HOST|INTERNAL_API" ./services | wc -l
14
$ crontab -l | grep -v "^#"
0 3 * * * /opt/scripts/nightly-report.sh --db=replica-2
*/15 * * * * curl -s https://internal-api:8443/healthFourteen references to internal services, plus two cron jobs nobody remembered writing down. This is exactly the kind of thing that gets missed when "map the dependencies" stays a mental note instead of an actual command someone runs.
During cutover
This is the highest-risk window, and the mistakes here tend to be about timing and access, not big technical decisions.
Lower DNS TTLs early
A 24-hour TTL turns a "quick" cutover into a full day. Lower it 48 hours ahead.
Build IAM from scratch
Copying old permissions recreates old mistakes. Rebuild with least-privilege as the default.
Set cost guardrails
Budgets and billing alerts should exist before migration, not after the invoice surprises you.
Have a rollback path
If you can't describe exactly how you'd reverse the cutover, you're not ready for it.
Checking the actual TTL before you touch anything takes one command, and it's the difference between a cutover that finishes in minutes and one that's still propagating a day later:
$ dig +noall +answer api.example.com
# before: 86400s = 24 hours
api.example.com. 86400 IN A 203.0.113.42
$ dig +noall +answer api.example.com
# after lowering, 48h ahead of cutover
api.example.com. 300 IN A 203.0.113.42After you're live
The migration isn't finished when the application responds to requests. It's finished when you can prove the new environment is actually equivalent, or better.
Rebuild monitoring first
Dashboards and alerts built for the old environment don't carry over automatically. Rebuild first.
Validate data integrity
Row counts and checksums on both sides, before you decommission the old environment.
Confirm compliance/region
A new region can mean new regulatory exposure. Don't assume it carried over.
Load and failure test
The happy path passing isn't the same as production-ready. Test load and failure both.
"It copied successfully" isn't the same as verified. A row count and a checksum on both sides is the difference between confidence and hope:
$ psql -h old-db -c "SELECT count(*), md5(string_agg(id::text,',')) FROM orders;"
count | md5
--------+----------------------------------
482913 | 7f9c2a1e4b8d3f0a6c5e1b9d2f4a8c3e
$ psql -h new-db -c "SELECT count(*), md5(string_agg(id::text,',')) FROM orders;"
count | md5
--------+----------------------------------
482913 | 7f9c2a1e4b8d3f0a6c5e1b9d2f4a8c3eCommon mistakes
- Treating it as pure lift-and-shift with no re-architecture review. Sometimes that's the right call, but it should be a decision, not a default born from time pressure.
- Underestimating the human side. Runbooks, on-call rotations, and team familiarity with the new environment matter as much as the technical migration itself.
- Scheduling cutover for a Friday. Give the team a full working week to catch and fix problems before the weekend, not after.
- No defined "done" criteria. Without an explicit checklist for what "migration complete" means, work quietly stalls at "mostly working."
What to do next
Most of these 12 items are cheap to fix when caught early and expensive when caught during cutover weekend. A Cloud Migration engagement plans the move in stages, tested at every step, with the dependency mapping, network review, and rollback plan built in from day one rather than assembled under pressure.

