AI & assistant-friendly summary

This section provides structured content for AI assistants and search engines. You can cite or summarize it when referencing this page.

Summary

AWS migration cost estimates are consistently wrong — not because the tools are bad, but because they miss the parallel run period, data transfer during migration, and the operational tax of learning a new environment. Here is what to actually model.

How to Migrate to AWS Without Cost Surprises

Cloud Architecture Palaniappan P 14 min read

Quick summary: AWS migration cost estimates are consistently wrong — not because the tools are bad, but because they miss the parallel run period, data transfer during migration, and the operational tax of learning a new environment. Here is what to actually model.

How to Migrate to AWS Without Cost Surprises
Table of Contents

AWS migration cost estimates are wrong with remarkable consistency. Not in random ways — they are wrong in the same ways across almost every project. They model the steady-state AWS environment accurately. They account for reserved instance discounts, instance right-sizing, and managed service replacements. What they miss is everything that happens in the gap between “current infrastructure” and “AWS infrastructure running in production.”

That gap has a cost. It includes weeks of parallel operation, one-time data transfer charges, DMS replication costs, license portability complications, and the operational overhead of a team learning a new environment. These costs are budgetable if you know to look for them. This guide covers what migration estimates miss, how to model the true total cost of a migration, and how to structure the migration itself to minimize cost overruns.

Why Migration Estimates Are Consistently Wrong

The typical AWS migration estimate uses one of two tools: AWS Migration Evaluator (formerly TSO Logic) or a spreadsheet-based TCO comparison. Both have the same blind spot: they model steady-state.

What the estimates include:

  • On-demand EC2 instance costs for migrated workloads
  • RDS or Aurora equivalent costs for migrated databases
  • Storage costs for EBS, S3, and EFS equivalents
  • Networking costs (approximate, often underestimated)
  • Reserved Instance or Savings Plan discounts (1-year or 3-year)
  • Support plan costs

What the estimates exclude:

  • The parallel run period (both old and new environments running simultaneously)
  • Data transfer costs during migration (initial bulk transfer + ongoing replication)
  • AWS Database Migration Service (DMS) costs
  • CloudWatch, CloudTrail, AWS Config, and Security Hub costs (enabled by default, often not modeled)
  • License portability complexity (Windows Server, SQL Server, Oracle)
  • The operational overhead of the first 3–6 months (more incidents, longer resolution times, more support cases)

The parallel run period alone adds 10–30% to the first-year migration cost for most projects. Teams that do not model it find their first-year savings estimate was too optimistic by that margin.

Lift-and-Shift vs Re-Architecture: The Three-Year Cost Calculus

The migration approach decision has a larger cost impact than any individual AWS service selection.

Lift-and-Shift (Rehost)

Lift-and-shift moves workloads to equivalent EC2 instances with minimal change. The application runs on AWS but retains on-premises architecture patterns: dedicated instances, static provisioning, manual scaling.

Year 1 cost profile:

  • Migration cost: low (AWS Application Migration Service is free for replication, you pay EC2 and EBS during cutover)
  • Operational cost: typically 10–20% lower than on-premises (no hardware refresh, reduced colocation costs)
  • Engineering cost: low (minimal application changes)

Year 3 cost profile:

  • Infrastructure running the same oversized instances, same static capacity
  • No benefit from auto-scaling, managed services, or serverless patterns
  • Technical debt accumulates (the application is “on AWS” but not “cloud-native”)

Lift-and-shift is the correct choice when: the migration timeline is fixed (datacenter contract expiring), the application is being decommissioned after migration, or the application is stable and low-traffic with limited optimization opportunity.

Re-Architecture (Re-Platform or Refactor)

Re-architecture converts on-premises workloads to use managed AWS services: EC2 to ECS/Fargate, self-managed databases to Aurora, on-premises queues to SQS.

Year 1 cost profile:

  • Migration cost: high (application changes, testing, parallel operation period is longer)
  • Operational cost: comparable to or slightly higher than on-premises during transition
  • Engineering cost: significant (refactoring takes time)

Year 3 cost profile:

  • Infrastructure scales with demand (auto-scaling, Fargate, serverless)
  • Managed services eliminate operational overhead (patching, backups, high availability)
  • Cost typically 30–50% below lift-and-shift equivalent at the same traffic level

The three-year TCO comparison for a representative web application (8 EC2 instances, 2 database servers, 5 TB storage):

ApproachYear 1Year 2Year 33-Year Total
On-premises (status quo)$180,000$180,000$180,000$540,000
Lift-and-shift$165,000 + $25,000 migration$145,000$140,000$475,000
Re-architecture$170,000 + $60,000 migration$120,000$90,000$440,000

Re-architecture has higher Year 1 cost but wins over 3 years. If your organization evaluates migrations on Year 1 ROI, lift-and-shift looks better. If you evaluate on 3-year TCO, re-architecture wins. Know which lens your organization uses before recommending an approach.

AWS Migration Evaluator: What It Tells You and What It Misses

Migration Evaluator collects performance data from your on-premises environment (via the Migration Evaluator Collector or on-premises agents) and generates a report estimating equivalent AWS costs with right-sizing recommendations.

What Migration Evaluator does well:

  • Identifies CPU and memory utilization to recommend appropriate EC2 instance families
  • Calculates 1-year and 3-year cost projections with Reserved Instance discounts
  • Flags instances with consistently low utilization (rightsizing candidates)
  • Provides a directionally accurate estimate for leadership presentations

What Migration Evaluator misses:

  • Database migration complexity (DMS costs, replication lag, validation effort)
  • Application-level dependencies that require simultaneous cutover
  • License portability costs (SQL Server on EC2 requires license verification; some licenses require Dedicated Hosts at $3.12/hour vs $0.19/hour for standard EC2)
  • The parallel run period
  • Networking costs in the migrated environment (NAT Gateway, data transfer, VPN)

Use Migration Evaluator for the steady-state estimate, then add a migration cost line item of 15–25% of Year 1 AWS cost to account for what it misses.

Data Transfer Economics During Migration

Initial data transfer to AWS is free — AWS does not charge for ingress. The costs are elsewhere.

Internet Transfer

For databases under 1 TB: internet transfer is fast and free (from AWS’s perspective). Your colocation or hosting provider may charge egress fees. Check your contract — egress fees at colocation facilities range from $0 (included in monthly commit) to $0.05–0.10/GB. A 500 GB database transfer at $0.08/GB egress: $40. Negligible.

For applications with ongoing replication during the parallel run period: DMS replication continuously transfers change data from source to target. The source egress costs accumulate over weeks.

AWS Snowball vs Direct Connect vs Internet

The decision framework for bulk data transfer:

Internet transfer:

  • Best for: under 10 TB, available bandwidth, no tight migration window
  • AWS ingress: free
  • Your egress: varies by provider

AWS Snowball Edge (80 TB usable):

  • Device cost: $300/device + $15/day (order to return, typically 10–14 days = $450–510 total)
  • Data transfer after loading: free
  • Best for: 10+ TB where internet would take days, constrained bandwidth, offline environments
  • Breakeven vs internet: when your data center egress cost × data volume > ~$500, or when speed is a constraint

AWS Direct Connect:

  • Setup: $0 for virtual interfaces, but requires cross-connect at a colocation ($50–200/month port fee)
  • Data transfer: $0.02/GB (vs $0.09/GB internet egress from AWS after migration)
  • Best for: ongoing hybrid connectivity post-migration, not one-time migration transfer
  • Migration use case: if you are establishing Direct Connect for hybrid connectivity anyway, route migration traffic through it

Database Migration Service Costs

DMS is not free. The cost model:

  • Replication instance: $0.065–0.26/hour depending on instance class (dms.r5.large = $0.26/hour = $187/month)
  • Storage: $0.023/GB/month for DMS replication storage
  • Data transfer: standard AWS data transfer rates if the target is in a different region

For a typical database migration with 4 weeks of ongoing replication (full load + change data capture until cutover): $187 × 1 month = $187/month for the replication instance. At dms.t3.medium = $0.065/hour: $47/month. DMS instance cost is modest; the cost that matters is the engineering time to configure, test, and validate the replication.

DMS Replication Task Terraform

resource "aws_dms_replication_instance" "main" {
  replication_instance_id    = "${var.project_name}-dms"
  replication_instance_class = "dms.r5.large"
  allocated_storage          = 50
  publicly_accessible        = false
  multi_az                   = false  # Single-AZ acceptable for migration (not HA)

  vpc_security_group_ids = [aws_security_group.dms.id]
  replication_subnet_group_id = aws_dms_replication_subnet_group.main.id

  engine_version               = "3.5.2"
  auto_minor_version_upgrade   = false
  allow_major_version_upgrade  = false

  tags = var.tags
}

resource "aws_dms_replication_subnet_group" "main" {
  replication_subnet_group_description = "DMS subnet group for ${var.project_name}"
  replication_subnet_group_id          = "${var.project_name}-dms-subnet-group"
  subnet_ids                           = var.private_subnet_ids

  tags = var.tags
}

resource "aws_dms_endpoint" "source" {
  endpoint_id   = "${var.project_name}-source"
  endpoint_type = "source"
  engine_name   = "mysql"  # or postgres, sqlserver, oracle

  server_name = var.source_db_host
  port        = 3306
  username    = var.source_db_username
  password    = var.source_db_password
  database_name = var.source_db_name

  tags = var.tags
}

resource "aws_dms_endpoint" "target" {
  endpoint_id   = "${var.project_name}-target"
  endpoint_type = "target"
  engine_name   = "aurora"

  server_name   = aws_rds_cluster.main.endpoint
  port          = 3306
  username      = var.target_db_username
  password      = var.target_db_password
  database_name = var.target_db_name

  tags = var.tags
}

resource "aws_dms_replication_task" "full_load_and_cdc" {
  migration_type            = "full-load-and-cdc"
  replication_instance_arn  = aws_dms_replication_instance.main.replication_instance_arn
  replication_task_id       = "${var.project_name}-migration"
  source_endpoint_arn       = aws_dms_endpoint.source.endpoint_arn
  target_endpoint_arn       = aws_dms_endpoint.target.endpoint_arn

  table_mappings = jsonencode({
    rules = [
      {
        rule-type   = "selection"
        rule-id     = "1"
        rule-name   = "include-all-tables"
        object-locator = {
          schema-name = var.source_db_name
          table-name  = "%"
        }
        rule-action = "include"
      }
    ]
  })

  replication_task_settings = jsonencode({
    TargetMetadata = {
      TargetSchema            = ""
      SupportLobs             = true
      FullLobMode             = false
      LobChunkSize            = 64
      LimitedSizeLobMode      = true
      LobMaxSize              = 32
    }
    FullLoadSettings = {
      TargetTablePrepMode = "DO_NOTHING"
      CreatePkAfterFullLoad = false
      StopTaskCachedChangesApplied = false
      StopTaskCachedChangesNotApplied = false
      MaxFullLoadSubTasks = 8
      TransactionConsistencyTimeout = 600
      CommitRate = 50000
    }
    Logging = {
      EnableLogging = true
    }
  })

  tags = var.tags
}

The full-load-and-cdc migration type performs an initial full load of all tables, then switches to Change Data Capture to keep the target in sync until cutover. This enables zero-downtime migration: the source and target stay synchronized until you are ready to cut over.

The Parallel Run Period: Budgeting What Everyone Skips

The parallel run period is the time between “AWS environment is ready” and “on-premises environment is decommissioned.” Both environments run simultaneously, and you pay for both.

The cost model is straightforward: (AWS monthly cost × parallel_weeks/4.3) + (on-premises weekly cost × parallel_weeks).

For a $5,000/month AWS environment with a 4-week parallel run:

  • AWS cost during parallel run: $5,000 × 4/4.3 = $4,651
  • On-premises cost continues: assume $8,000/month current spend = $8,000 × 4/4.3 = $7,442
  • Total parallel run period cost: ~$12,093
  • Net savings if parallel run was not in the estimate: you spent $12,093 you did not plan for

Typical parallel run durations by migration type:

  • Simple web application, no compliance requirements: 1–2 weeks
  • Production transactional system, business-hours validation: 2–4 weeks
  • Regulated environment (healthcare, financial services): 6–12 weeks

The parallel run is not waste. It is the cost of validating that the migration is safe. Budget it explicitly in your migration estimate as a line item, not hidden in contingency.

Gradual Traffic Cutover with Route 53 Weighted Routing

Cutting over 100% of traffic immediately from old to new infrastructure is high-risk. Weighted routing allows a gradual shift: 10% to new, validate, then 50%, then 100%. Route 53 weighted routing Terraform:

# Phase 1: 10% to new AWS environment
resource "aws_route53_record" "legacy" {
  zone_id = var.hosted_zone_id
  name    = var.domain_name
  type    = "A"

  weighted_routing_policy {
    weight = 90  # Reduce as validation progresses
  }

  set_identifier = "legacy-datacenter"

  alias {
    name                   = var.legacy_load_balancer_dns
    zone_id                = var.legacy_load_balancer_zone_id
    evaluate_target_health = true
  }
}

resource "aws_route53_record" "aws_new" {
  zone_id = var.hosted_zone_id
  name    = var.domain_name
  type    = "A"

  weighted_routing_policy {
    weight = 10  # Increase as validation progresses
  }

  set_identifier = "aws-new"

  alias {
    name                   = aws_lb.main.dns_name
    zone_id                = aws_lb.main.zone_id
    evaluate_target_health = true
  }
}

Update the weights by changing Terraform variables and applying:

  • Day 1: legacy = 90, aws = 10
  • Day 3 (after monitoring): legacy = 50, aws = 50
  • Day 7 (after business cycle validation): legacy = 0, aws = 100

Route 53 weights are not exact percentages — they are proportional ratios. A weight of 90 and 10 means approximately 90% and 10% of traffic (Route 53 does not guarantee exact split, especially at low traffic volumes).

Collecting Baseline Metrics with CloudWatch Agent

Right-sizing requires performance data from the source environment. The CloudWatch Agent can collect from on-premises servers before migration:

{
  "agent": {
    "metrics_collection_interval": 60,
    "run_as_user": "cwagent"
  },
  "metrics": {
    "namespace": "MigrationBaseline",
    "append_dimensions": {
      "ServerName": "${aws:ec2-instance-id}",
      "Environment": "on-premises",
      "MigrationProject": "2026-q1-migration"
    },
    "metrics_collected": {
      "cpu": {
        "resources": ["*"],
        "measurement": [
          "cpu_usage_active",
          "cpu_usage_iowait",
          "cpu_usage_steal"
        ],
        "totalcpu": true
      },
      "mem": {
        "measurement": [
          "mem_used_percent",
          "mem_available_percent"
        ]
      },
      "disk": {
        "resources": ["/", "/data"],
        "measurement": [
          "disk_used_percent",
          "disk_read_bytes",
          "disk_write_bytes",
          "disk_read_time",
          "disk_write_time"
        ]
      },
      "diskio": {
        "resources": ["*"],
        "measurement": [
          "diskio_reads",
          "diskio_writes",
          "diskio_read_bytes",
          "diskio_write_bytes",
          "diskio_iops_in_progress"
        ]
      },
      "net": {
        "resources": ["eth0"],
        "measurement": [
          "net_bytes_sent",
          "net_bytes_recv",
          "net_packets_sent",
          "net_packets_recv"
        ]
      }
    }
  }
}

Install the CloudWatch Agent on source servers 30 days before the planned migration. This gives you a full month of data including any business cycle peaks (month-end processing, weekly batch jobs, peak traffic periods). The CloudWatch Agent on on-premises servers sends metrics to CloudWatch via the internet (free ingress) and you pay standard CloudWatch custom metrics charges ($0.30/metric/month$30–50/month for a 10-server environment, worth every dollar for right-sizing accuracy).

Right-Sizing: The Before/After Comparison

The right-sizing methodology:

  1. Collect p50 and p95 CPU and memory from baseline metrics over 30 days
  2. Map to AWS instance families:
    • CPU-bound (p95 CPU > 60%, memory < 60%): c7g (ARM, 20% cheaper) or c6i (x86)
    • Memory-bound (memory p95 > 70%): r7g or r6i
    • Balanced: m7g or m6i
  3. Size to p95 utilization + 30% headroom (accounts for traffic growth during validation period)
  4. Plan a right-size review at day 30 post-migration

Initial migration instances are typically 20–30% oversized. This is intentional — you do not want to discover an instance is too small during the parallel run period. The day-30 right-size review converts the migration instances to the appropriate production size. This is also when you purchase Savings Plans (after you know the production instance family and size).

Rollback Cost Complexity

Every migration plan assumes rollback is free. It is not.

Rollback scenarios and costs:

  • DNS rollback: Change Route 53 weights back to legacy. Cost: $0. Time: DNS propagation (TTL, typically 60–300 seconds).
  • Database rollback: If DMS is running continuous replication, the source database is current and rollback is a DNS change. If DMS was stopped at cutover, rollback requires reverse replication (manual or DMS reverse task). Cost: 1–2 days of DMS replication instance ($10–50).
  • Full environment rollback after 30+ days on AWS: Old environment has been decommissioned. Rollback requires re-provisioning on-premises hardware or negotiating emergency capacity with your former provider. This is not a real rollback option — it is a crisis.

The practical conclusion: keep the parallel run period active (both environments running) until you have at least one full business cycle of production data confirming the AWS environment performs correctly. Decommissioning the legacy environment on day 8 to “save money” removes your real rollback option.

The Invisible AWS Costs: Services Enabled By Default

When you create a new AWS account or a new account in AWS Organizations, several services start generating charges immediately:

  • CloudTrail: By default, management events are free (first delivery of management events per region). Data events cost $0.10/100,000 events. If your application generates high S3 or Lambda traffic and you enable data events in CloudTrail for security, budget $50–500/month.
  • AWS Config: $0.003/configuration item recorded. A production account with 500 resources recording configuration changes daily: ~$450/month. Config is often enabled by compliance requirements; it is not free.
  • Amazon GuardDuty: $0.50–$4.00/million events analyzed, varies by data source. A typical production account: $50–150/month for GuardDuty findings.
  • VPC Flow Logs: $0.50/GB for logs stored in CloudWatch Logs. If enabled for all traffic (accept and reject), a high-traffic application generates gigabytes of flow logs daily. Store in S3 ($0.023/GB) instead of CloudWatch Logs for 95% cost reduction.
  • Systems Manager Session Manager: Free for basic sessions; $0.00007/minute for advanced instances.

Model these costs explicitly in your migration estimate. They are real, recurring, and often omitted.

Edge Cases in Migration Cost Modeling

Partial Migrations

Not everything migrates at the same time. A partial migration where 30% of the application runs on AWS and 70% on-premises means full costs for both environments, plus cross-environment networking costs: VPN ($0.05/hour + $0.10/GB) or Direct Connect (circuit cost). Model the hybrid state explicitly, including the networking overhead.

Legacy License Portability

SQL Server on EC2 using BYOL requires Software Assurance. If your SQL Server licenses do not include SA, you cannot use BYOL on EC2. Options: license-included EC2 (more expensive), RDS SQL Server (managed, license included, but cannot bring your own), or migrate to Aurora PostgreSQL or MySQL (requires application changes). The license portability analysis should happen before the migration estimate, not during.

Oracle licenses have additional complexity: Oracle has an audit clause that counts EC2 vCPUs differently than physical cores. A rule of thumb used by Oracle for license counting on AWS: all vCPUs in the account potentially count, not just the ones running Oracle. Engage an Oracle license specialist before migrating Oracle workloads to EC2.

DMS Unsupported Data Types

DMS does not support all data types in all databases. PostgreSQL array types, spatial data, and some custom types require manual migration or custom transformation rules. Discovering unsupported data types after starting the DMS task adds days of rework to the migration timeline. Run DMS Schema Conversion Tool before the migration estimate to identify unsupported types.

For the strategic framework on choosing your migration approach, see our guide on AWS migration strategy. For the data transfer cost implications that extend beyond the migration itself, see AWS data transfer costs for startups. For a comprehensive cost governance approach to manage costs after migration is complete, see FinOps on AWS: Complete Guide to Cloud Cost Governance.

PP
Palaniappan P

AWS Cloud Architect & AI Expert

AWS-certified cloud architect and AI expert with deep expertise in cloud migrations, cost optimization, and generative AI on AWS.

AWS ArchitectureCloud MigrationGenAI on AWSCost OptimizationDevOps

Ready to discuss your AWS strategy?

Our certified architects can help you implement these solutions.

Recommended Reading

Explore All Articles »