
AWS Data Transfer Cheat Sheet πΊοΈ
Understanding AWS Data Transfer rates and save money. Simple Terraform setup.
Series: Save on AWS with Terraform (21 articles)
- β¦
- 20AWS 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 β To | Cost/GB | Notes |
|---|---|---|
| EC2 β EC2 (same AZ) | Free β | Use private IP only |
| EC2 β EC2 (same AZ, public IP) | $0.02 | Private IP = free, public IP = paid π€¦ |
| EC2 β EC2 (cross-AZ) | $0.02 | $0.01 out + $0.01 in |
| EC2 β EC2 (cross-region) | $0.02 | Varies by region pair |
| EC2 β S3 (same region) | Free β | Via VPC Gateway Endpoint |
| EC2 β S3 (same region, no endpoint) | $0.09 | Goes through NAT Gateway πΈ |
| EC2 β DynamoDB (same region) | Free β | Via VPC Gateway Endpoint |
| EC2 β RDS (same AZ) | Free β | |
| EC2 β RDS (cross-AZ) | $0.02 | Multi-AZ adds this automatically |
| S3 β CloudFront | Free β | CloudFront origin fetches are free |
| S3 β Internet | $0.09 | First 10TB/month |
| CloudFront β Internet | $0.085 | Cheaper than S3 direct + 1TB/month free |
| NAT Gateway processing | $0.045 | On top of any other transfer fees |
Traffic to Internet
| Volume/Month | S3/EC2 Direct | Via CloudFront |
|---|---|---|
| First 1TB | $0.09/GB | Free (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 FREECloudFront 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
| Problem | Fix | Savings | Effort |
|---|---|---|---|
| S3/DynamoDB via NAT Gateway | VPC Gateway Endpoints | $0.135/GB β Free | 5 min |
| S3 direct to internet | CloudFront in front | $0.09/GB β Free (1TB) | 20 min |
| Public IP same-AZ traffic | Use private IPs | $0.02/GB β Free | 10 min |
| All traffic via NAT Gateway | Interface Endpoints for AWS services | $0.045/GB saved | 15 min |
| Full bucket cross-region replication | Filter by prefix | 50-90% less transfer | 10 min |
| Cross-AZ microservice chatter (non-prod) | Pin to same AZ | $0.02/GB β Free | 10 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)
- β¦
- 20AWS Data Transfer Cheat Sheet πΊοΈ This article
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article