Skip to content
MORPHLIXTalk to Morphlix
All resources
IntermediateMigration18 min read

Zero-downtime cutover on AWS

The full rehearsal sequence we run before any production migration — traffic shadowing, dual writes, progressive reads, and the rollback switch we hope never to flip.

Last updated August 2026

Before you start

An AWS account with admin access to the target environment
Familiarity with CloudFormation or Terraform
A target database provisioned and reachable
Baseline latency and error-rate metrics for the current system
01

Why the window has to go

A retail estate that closes for four hours on a Sunday loses a Sunday. A payments platform that closes for four hours loses trust. The techniques below are not new — shadowing, dual writes, and progressive cutover have been documented for years — but they have moved from premium option to baseline expectation, and that changes the shape of the plan. Most of the effort happens before anything moves.

Rule of thumb: if the cutover hour is the interesting part of your plan, the plan is not finished.

02

Set up traffic shadowing

Mirror a copy of production traffic to the new stack and compare every response. Nothing is served from the new system yet — you are looking for disagreement. Route 53 weighted records or an ALB rule can fan the copy out; the important part is capturing both responses and diffing them structurally rather than byte for byte.

yamlcloudformation
ShadowTargetGroup:
  Type: AWS::ElasticLoadBalancingV2::TargetGroup
  Properties:
    Name: !Sub '${AppName}-shadow'
    Port: 8080
    Protocol: HTTP
    VpcId: !Ref VpcId
    HealthCheckPath: /healthz

ShadowRule:
  Type: AWS::ElasticLoadBalancingV2::ListenerRule
  Properties:
    ListenerArn: !Ref HttpsListener
    Priority: 10
    Actions:
      - Type: forward
        ForwardConfig:
          TargetGroups:
            - TargetGroupArn: !Ref LegacyTargetGroup
              Weight: 100
            - TargetGroupArn: !Ref ShadowTargetGroup
              Weight: 0        # mirrored, never served
03

Dual writes and reconciliation

Once reads agree, start writing to both systems. Run a reconciliation job nightly that compares row counts and checksums per table, and treat any non-zero delta as a blocking defect rather than a known issue. This is the stage teams are tempted to rush; it is also the stage that decides whether the cutover is boring.

bashreconciliation check
# nightly delta check — fails the pipeline on any drift
aws dms describe-table-statistics \
  --replication-task-arn "$TASK_ARN" \
  --query 'TableStatistics[?ValidationFailedRecords>`0`]' \
  --output table

DELTA=$(aws dms describe-table-statistics \
  --replication-task-arn "$TASK_ARN" \
  --query 'sum(TableStatistics[].ValidationFailedRecords)')

if [ "$DELTA" -ne 0 ]; then
  echo "Validation drift: $DELTA records — cutover blocked"
  exit 1
fi
04

Shift reads progressively

Move read traffic in slices — one percent, then five, then twenty-five — watching p99 latency and your error budget at each step. Hold each slice for at least one full traffic cycle, which for most businesses means a working day rather than an hour. If a slice degrades, shift back and diagnose before proceeding.

Automate the rollback trigger. A human watching a dashboard at 2am is not a control.

05

Cut writes, then hold

The write cutover is short — usually minutes — because everything is already in place. Afterwards, keep the old estate running and reverse replication armed for a full business cycle, including the month-end run if you have one. Only then decommission. The cost of a fortnight of duplicate infrastructure is trivial against the cost of discovering a month-end batch that only existed on the old system.

bashcutover
# 1. stop writes at the edge
aws elbv2 modify-rule --rule-arn "$MAINT_RULE" \
  --actions Type=fixed-response,FixedResponseConfig="{StatusCode=503}"

# 2. drain in-flight, confirm replication lag is zero
while [ "$(replication_lag_seconds)" -gt 0 ]; do sleep 2; done

# 3. promote target, flip weights
aws rds promote-read-replica --db-instance-identifier "$TARGET_DB"
aws elbv2 modify-rule --rule-arn "$SHADOW_RULE" \
  --actions Type=forward,TargetGroupArn="$NEW_TG"

# 4. reverse replication stays armed for 14 days

Was this useful?

Tell us what to write next.

Rather not do it yourself

We run this as
a scoped engagement.

Everything in this guide is something we deliver for clients — with the rehearsal, the rollback, and the handover documentation included.