AWS Builder Center
AWS Data Transfer Cheat Sheet πŸ—ΊοΈ

AWS Data Transfer Cheat Sheet πŸ—ΊοΈ

Understanding AWS Data Transfer rates and save money. Simple Terraform setup.

Series: Save on AWS with Terraform (21 articles)

  1. …
  2. 20
    AWS Data Transfer Cheat Sheet πŸ—ΊοΈ This article

AWS charges differently for same-AZ, cross-AZ, cross-region, and internet traffic - and most teams have no idea which one is bleeding money. Here's every scenario explained with Terraform patterns to minimize each.

Ask any AWS engineer: "How much does data transfer cost?"
Watch them struggle πŸ˜•
That's because AWS has at least 15 different data transfer prices depending on where data goes, which service sends it, and whether you remembered to use a VPC endpoint.
Nobody memorizes this. So I built the cheat sheet I wish I had, plus Terraform patterns to fix each one.

πŸ“Š The Master Cheat Sheet

Bookmark this. You'll need it.

Traffic Within AWS

From β†’ ToCost/GBNotes
EC2 β†’ EC2 (same AZ)Free βœ…Use private IP only
EC2 β†’ EC2 (same AZ, public IP)$0.02Private IP = free, public IP = paid 🀦
EC2 β†’ EC2 (cross-AZ)$0.02$0.01 out + $0.01 in
EC2 β†’ EC2 (cross-region)$0.02Varies by region pair
EC2 β†’ S3 (same region)Free βœ…Via VPC Gateway Endpoint
EC2 β†’ S3 (same region, no endpoint)$0.09Goes through NAT Gateway πŸ’Έ
EC2 β†’ DynamoDB (same region)Free βœ…Via VPC Gateway Endpoint
EC2 β†’ RDS (same AZ)Free βœ…
EC2 β†’ RDS (cross-AZ)$0.02Multi-AZ adds this automatically
S3 β†’ CloudFrontFree βœ…CloudFront origin fetches are free
S3 β†’ Internet$0.09First 10TB/month
CloudFront β†’ Internet$0.085Cheaper than S3 direct + 1TB/month free
NAT Gateway processing$0.045On top of any other transfer fees

Traffic to Internet

Volume/MonthS3/EC2 DirectVia CloudFront
First 1TB$0.09/GBFree (free tier) βœ…
1-10TB$0.09/GB$0.085/GB
10-50TB$0.085/GB$0.080/GB
50-150TB$0.070/GB$0.060/GB
CloudFront is cheaper than direct S3 at every tier. Yes, adding a CDN saves money. 🀯

πŸ’Έ The 5 Biggest Data Transfer Traps

Trap #1: NAT Gateway Double Tax

This is the most expensive mistake teams make.
1
2
3
4
5
6
7
8
9
10
Private EC2 β†’ NAT Gateway β†’ Internet β†’ S3

You pay:
NAT Gateway processing: $0.045/GB
Data transfer out: $0.09/GB
Total: $0.135/GB 😱

Fix with VPC Endpoint:
Private EC2 β†’ VPC Endpoint β†’ S3
Total: $0.00/GB βœ…
Terraform fix:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# FREE S3 access from private subnets
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"

route_table_ids = var.private_route_table_ids

tags = { Name = "s3-gateway-endpoint" }
}

# FREE DynamoDB access from private subnets
resource "aws_vpc_endpoint" "dynamodb" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.dynamodb"
vpc_endpoint_type = "Gateway"

route_table_ids = var.private_route_table_ids

tags = { Name = "dynamodb-gateway-endpoint" }
}
Savings: $0.135/GB β†’ $0.00/GB. For 1TB/month of S3 traffic = $135/month saved.

Trap #2: Cross-AZ Traffic You Don't Know About

Multi-AZ is great for availability. But every byte between AZs costs $0.02/GB.
Common hidden cross-AZ traffic:
  • App server in AZ-a talking to Redis in AZ-b
  • Microservices spread across AZs calling each other
  • RDS Multi-AZ synchronous replication
Terraform fix β€” Keep tightly coupled services in the same AZ:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Pin your app and its cache to the same AZ
resource "aws_instance" "app" {
subnet_id = aws_subnet.private_az_a.id # AZ-a
# ...
}

resource "aws_elasticache_replication_group" "redis" {
preferred_cache_cluster_azs = ["us-east-1a"] # Same AZ
# ...
}

# For ECS β€” use placement constraints
resource "aws_ecs_service" "api" {
# ...
placement_constraints {
type = "memberOf"
expression = "attribute:ecs.availability-zone == us-east-1a"
}
}
⚠️ Trade-off: Pinning to one AZ reduces availability. Do this for dev/staging, not production. In prod, accept cross-AZ cost as the price of HA.

Trap #3: Public IP When Private IP Works

This one is sneaky. Two EC2 instances in the same AZ communicating via public IP costs $0.02/GB. Via private IP? Free.
1
2
3
4
5
EC2-A (public IP) β†’ Internet Gateway β†’ EC2-B (public IP)
Cost: $0.02/GB

EC2-A (private IP) β†’ EC2-B (private IP)
Cost: $0.00/GB βœ…
Terraform fix:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Don't assign public IPs to instances that don't need them
resource "aws_instance" "backend" {
subnet_id = aws_subnet.private.id
associate_public_ip_address = false # πŸ‘ˆ No public IP

tags = { Name = "backend-server" }
}

# Use internal ALB for service-to-service communication
resource "aws_lb" "internal" {
name = "internal-api"
internal = true # πŸ‘ˆ Internal, no internet-facing
load_balancer_type = "application"
subnets = var.private_subnet_ids
}

Trap #4: S3 Direct Download Instead of CloudFront

If you're serving files to users directly from S3, you're paying more than if you put CloudFront in front.
1
2
S3 β†’ Internet: $0.09/GB + no free tier
CloudFront β†’ Internet: $0.085/GB + 1TB/month FREE
CloudFront is literally cheaper AND faster. There's no reason not to use it for public assets.
Terraform fix:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
resource "aws_cloudfront_distribution" "assets" {
enabled = true
is_ipv6_enabled = true

origin {
domain_name = aws_s3_bucket.assets.bucket_regional_domain_name
origin_id = "s3-assets"
origin_access_control_id = aws_cloudfront_origin_access_control.s3.id
}

default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "s3-assets"
viewer_protocol_policy = "redirect-to-https"

forwarded_values {
query_string = false
cookies { forward = "none" }
}

# Cache for 24 hours
min_ttl = 0
default_ttl = 86400
max_ttl = 604800
}

restrictions {
geo_restriction { restriction_type = "none" }
}

viewer_certificate {
cloudfront_default_certificate = true
}

tags = { Name = "asset-cdn" }
}

resource "aws_cloudfront_origin_access_control" "s3" {
name = "s3-assets-oac"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
Savings on 2TB/month: S3 direct = $180/mo β†’ CloudFront = $85/mo (first 1TB free). $95/month saved. πŸ’°

Trap #5: Cross-Region Replication Without Thinking

S3 cross-region replication, RDS cross-region read replicas, DynamoDB global tables β€” all incur $0.02/GB transfer.
1
2
3
S3 Replication: us-east-1 β†’ eu-west-1
100GB/month of changes = $2/month (seems cheap)
10TB/month of changes = $200/month (not cheap) 😬
Terraform fix β€” Replicate selectively:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
resource "aws_s3_bucket_replication_configuration" "selective" {
bucket = aws_s3_bucket.primary.id
role = aws_iam_role.replication.arn

rule {
id = "critical-data-only"
status = "Enabled"

# Only replicate what matters, not everything
filter {
prefix = "critical/" # πŸ‘ˆ Not the entire bucket
}

destination {
bucket = aws_s3_bucket.replica.arn
storage_class = "STANDARD_IA" # Cheaper storage in replica
}
}
}

πŸ“Š Data Transfer Cost Monitor

Deploy this to track where your data transfer money goes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# monitoring/data-transfer.tf

resource "aws_ce_anomaly_monitor" "data_transfer" {
name = "data-transfer-anomaly-monitor"
monitor_type = "DIMENSIONAL"
monitor_dimension = "SERVICE"
}

resource "aws_ce_anomaly_subscription" "data_transfer" {
name = "data-transfer-alerts"

monitor_arn_list = [aws_ce_anomaly_monitor.data_transfer.arn]

frequency = "DAILY"

threshold_expression {
dimension {
key = "ANOMALY_TOTAL_IMPACT_ABSOLUTE"
values = ["50"] # Alert if anomaly > $50
match_options = ["GREATER_THAN_OR_EQUAL"]
}
}

subscriber {
type = "EMAIL"
address = var.alert_email
}
}

# CloudWatch dashboard for data transfer visibility
resource "aws_cloudwatch_dashboard" "data_transfer" {
dashboard_name = "data-transfer-costs"

dashboard_body = jsonencode({
widgets = [
{
type = "metric"
x = 0
y = 0
width = 12
height = 6
properties = {
metrics = [
["AWS/EC2", "NetworkOut", { stat = "Sum", period = 86400 }],
["AWS/EC2", "NetworkIn", { stat = "Sum", period = 86400 }]
]
title = "EC2 Network Traffic (Daily)"
region = var.region
}
},
{
type = "metric"
x = 12
y = 0
width = 12
height = 6
properties = {
metrics = [
["AWS/NATGateway", "BytesOutToDestination", { stat = "Sum", period = 86400 }],
["AWS/NATGateway", "BytesOutToSource", { stat = "Sum", period = 86400 }]
]
title = "NAT Gateway Traffic (Daily)"
region = var.region
}
}
]
})
}

⚑ Quick Audit: Where's Your Money Going?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Check data transfer costs by service (last 30 days)
aws ce get-cost-and-usage \
--time-period Start=$(date -u -d '30 days ago' +%Y-%m-%d),End=$(date -u +%Y-%m-%d) \
--granularity MONTHLY \
--metrics BlendedCost \
--filter '{
"Dimensions": {
"Key": "USAGE_TYPE_GROUP",
"Values": ["EC2: Data Transfer - Internet (Out)",
"EC2: Data Transfer - Region to Region (Out)",
"EC2: Data Transfer - Inter AZ"]
}
}'
\
--group-by Type=DIMENSION,Key=USAGE_TYPE_GROUP \
--output table

πŸ’‘ Quick Reference: What to Fix First

ProblemFixSavingsEffort
S3/DynamoDB via NAT GatewayVPC Gateway Endpoints$0.135/GB β†’ Free5 min
S3 direct to internetCloudFront in front$0.09/GB β†’ Free (1TB)20 min
Public IP same-AZ trafficUse private IPs$0.02/GB β†’ Free10 min
All traffic via NAT GatewayInterface Endpoints for AWS services$0.045/GB saved15 min
Full bucket cross-region replicationFilter by prefix50-90% less transfer10 min
Cross-AZ microservice chatter (non-prod)Pin to same AZ$0.02/GB β†’ Free10 min
Start with VPC Gateway Endpoints. They're free, take 5 minutes, and eliminate the most expensive trap on this list. 🎯

πŸ“Š TL;DR

1
2
3
4
5
6
7
8
9
Same AZ, private IP: FREE βœ… (always use this)
Same AZ, public IP: $0.02 (why??)
Cross-AZ: $0.02 (Multi-AZ tax)
Cross-Region: $0.02 (replicate selectively)
To Internet: $0.09 (use CloudFront instead)
Via NAT Gateway: +$0.045 on top (use VPC Endpoints)
S3/DynamoDB via Endpoint: FREE βœ… (no-brainer)
S3 β†’ CloudFront: FREE βœ… (origin fetch is free)
CloudFront β†’ Internet: $0.085 + 1TB free (cheaper than S3 direct)
Bottom line: Data transfer is the most confusing line item on your AWS bill β€” and that's by design. Print this cheat sheet, tape it to your monitor, and stop paying for traffic that should be free. πŸ–¨οΈ

I bet you don't have VPC Gateway Endpoints for S3 yet. Go deploy them right now β€” it's 5 lines of Terraform and saves $0.135 on every GB. I'll wait. πŸ˜€
Found this helpful? Follow for more AWS cost optimization with Terraform! πŸ’¬

Series: Save on AWS with Terraform (21 articles)

  1. …
  2. 20
    AWS Data Transfer Cheat Sheet πŸ—ΊοΈ This article
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article