Skip to content

Writing

Moving a legacy application to AWS without downtime: a sequencing guide

The order of operations that keeps a migration reversible at every step — what to move first, why the database goes last, and the cost lines that surprise teams after the cutover.

6 min read

Most migration plans fail on sequencing rather than on technology. A team moves the database first because it feels foundational, discovers the application cannot tolerate the added latency, and now has a half-migrated system with no way back.

The principle underneath everything here: every step must be reversible on its own. If any single step cannot be undone in minutes, it is too large and needs splitting.

Before anything moves: three things to establish

  1. Know what you actually have

    Every cron job, every file written to local disk, every hard-coded IP, every certificate and its expiry. The surprises in a migration are almost always something nobody documented — a nightly script on a machine no one has logged into for two years.

  2. Measure the current system

    p50 and p95 latency, requests per second, peak concurrency, database size and growth. You cannot claim an improvement, or notice a regression, without a baseline taken beforehand.

  3. Write the rollback for step one

    If you cannot write it, the step is wrong. This is the discipline that keeps a migration from becoming a one-way door.

The order that keeps every step reversible

#StepReversible by
1DNS in front of the old system, low TTLNothing has moved
2Static assets and media to S3 + CloudFrontPoint the URLs back
3Stateless app on ECS/EKS, still using the old databaseShift traffic weight to zero
4Background jobs and cronsRe-enable the old scheduler
5Database, with replication and a short cutoverFail back to the old primary
6Decommission — only after a full cycleNot reversible; hence last
Traffic management first, stateless second, state last. The database is the hardest thing to reverse, so it moves when everything else is proven.

Step 1: put a traffic layer in front before you need it

Move DNS to Route 53 and lower the TTL to 60 seconds weeks ahead. TTL changes take the old TTL to propagate, so a 24-hour TTL lowered on cutover day is still a 24-hour TTL on cutover day. This is the most common avoidable delay in a migration.

Step 3: the stateless tier, running against the old database

This is the step that de-risks everything after it. The new application runs in AWS and talks to the existing database over a VPN or Direct Connect. It proves the container image, the configuration, the secrets, the logging and the health checks — while the data has not moved and rollback is a weight change.

The application usually needs two changes to be movable at all, and both are worth making regardless:

  1. Nothing may be written to local disk and expected to persist

    Uploads to S3, sessions to Redis or a cookie, logs to stdout. A container's filesystem is ephemeral, and this is the assumption legacy applications most often violate.

  2. Configuration comes from the environment, at start-up

    Not from a file baked into a build. Otherwise you cannot promote one artefact across environments, and you are rebuilding per environment — which means you did not test what you shipped.

Step 4: background jobs need explicit ownership

Crons are where double-execution happens. During the transition both systems can run the same nightly job, and the second one either fails loudly or — worse — quietly does the work twice.

Move them with an explicit switch, one job at a time, and make each job idempotent before it moves.

Step 5: the database, and the only part that needs a window

Everything else has been reversible. This is the step that needs care.

  1. Replicate continuously into the target, and let it catch up

    DMS or native replication. Run it for days, not hours, and watch replication lag until it is consistently near zero.

  2. Verify, do not assume

    Row counts per table, checksums on the largest ones, and a sample of business-level invariants — order totals, balances. "Replication reported success" is not verification.

  3. Cut over in a short, planned window

    Stop writes, let replication drain to zero, promote the target, point the application at it, resume. Minutes, and rehearsed at least once against production-sized data.

  4. Keep replication running the other way for a day

    So failing back remains possible until you are certain.

The cost lines that surprise people

Compute is the line everyone estimates and rarely the one that surprises.

LineWhy it surprisesWhat reduces it
NAT Gateway dataCharged per GB processed; chatty private subnets add upVPC endpoints for S3 and DynamoDB
Cross-AZ transferCharged in both directions between availability zonesAZ-aware routing; co-locate chatty pairs
CloudWatch Logs ingestionPriced per GB ingested, not storedSample debug logs; shorter retention
Load balancer capacity unitsCharged on connections and rules, not just trafficConsolidate listeners; prune rules
RDS storage IOPSProvisioned separately from the instanceRight-size after observing real IOPS
Snapshots and backupsAccumulate silentlyA lifecycle policy from day one
Data transfer and managed-service premiums are where post-migration bills exceed the estimate.
  1. Tag everything from the first resource, and enforce it in CI

    Owner, environment, service. Retro-tagging a live account is miserable, and without tags you cannot attribute a bill.

  2. Right-size after two weeks of real traffic, not before

    Sizing on the old hardware's specification usually over-provisions substantially.

  3. Buy commitments only after that

    Savings Plans on an unsettled workload lock in the wrong shape for a year.

What "done" means

Decommissioning is a step, not an afterthought, and it comes after a full business cycle on the new system — a month-end, a payroll run, whatever your peak actually is.

  1. One full cycle passed on the new system

    Including the periodic jobs that only run monthly, which are the ones nobody tested.

  2. Failback exercised at least once, deliberately

    A rollback path nobody has used is a rollback path you do not have.

  3. Runbooks and dashboards updated to the new architecture

    An on-call engineer following a runbook that describes the old system is worse off than one with no runbook.

  4. Then, and only then, turn the old system off

    Keep a final snapshot longer than you think you need it.

Start here

Tell us what you are building

Or what is breaking, or what has to go faster. You will get a straight answer from an engineer who would do the work.