Data Transfer: The Silent Killer
Data transfer is the tax AWS collects on every useful thing your system does. It does not appear on any infrastructure diagram. Nobody provisions it. It grows proportionally with your success — more users, more traffic, higher bill — and then keeps growing because architects assumed it was free. It is not free. At $0.09/GB out to the internet and $0.01/GB cross-AZ, it routinely accounts for 20–30 % of bills for data-intensive systems.
Anatomy of AWS Transfer Costs
Not all bytes are priced equally. The transfer cost hierarchy:
| Traffic Type | Price | Notes |
|---|---|---|
| Internet egress (first 10 TB) | $0.09/GB | Per region; lower at higher tiers |
| Internet egress via CloudFront | $0.0085–$0.085/GB | Up to 6× cheaper |
| Cross-AZ (same region) | $0.01/GB each way | $0.02/GB round trip |
| Same-AZ | Free | Requires explicit AZ affinity |
| AWS to AWS (different region) | $0.02/GB | Even within same account |
| AWS PrivateLink / VPC Endpoint | $0.01/GB | Cheaper than NAT for S3/DynamoDB |
| S3 Transfer Acceleration | +$0.04–$0.08/GB | Additive to egress |
The cross-AZ charge is the most surprising. At $0.02/GB round-trip, a service making 100 GB/day of cross-AZ calls pays $730/year per inter-service pair. At 50 service pairs in a microservices architecture, that is $36,500/year on internal traffic.
Finding Transfer Costs in CUR
-- All transfer line items, sorted by cost
SELECT
line_item_usage_type,
line_item_product_code,
product_from_location,
product_to_location,
ROUND(SUM(line_item_usage_amount), 0) AS gb_transferred,
ROUND(SUM(line_item_unblended_cost), 2) AS cost_usd
FROM cur_db.cur_table
WHERE line_item_usage_start_date >= DATE_TRUNC('month', CURRENT_DATE)
AND (
line_item_usage_type LIKE '%DataTransfer%'
OR line_item_usage_type LIKE '%Bytes%'
OR line_item_usage_type LIKE '%NatGateway%'
)
AND line_item_unblended_cost > 0
GROUP BY 1, 2, 3, 4
ORDER BY cost_usd DESC
LIMIT 30;Run this monthly. Any single usage type above $1,000 deserves investigation.
NAT Gateway: The Biggest Surprise
NAT Gateway charges $0.045/GB processed plus $0.045/hr per gateway. A busy NAT Gateway seeing 5 TB/month of traffic:
Processing: 5,000 GB × $0.045 = $225
Hourly: 730 hr × $0.045 = $32.85
Monthly total: $257.85Multiply by the number of private subnets needing internet access. The solution is almost always S3 and DynamoDB Gateway Endpoints, which are free.
# Replace NAT Gateway traffic to S3 and DynamoDB with free Gateway Endpoints
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"
team = var.team
}
}
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"
team = var.team
}
}After deploying these, S3 and DynamoDB traffic routes through the endpoint rather than the NAT Gateway. The reduction in NAT Gateway data processing charges is typically 40–70 % for data-heavy workloads.
Cross-AZ Traffic Architecture
Force same-AZ routing in Kubernetes with topology spread constraints:
# Deployment patch — prefer same-AZ routing
apiVersion: apps/v1
kind: Deployment
metadata:
name: service-b
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: service-bAnd for services in AWS Load Balancer Controller, enable AZ affinity:
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.kubernetes.io/aws-load-balancer-target-group-attributes: >
target_group_health.dns_failover.minimum_healthy_targets.count=1,
load_balancing.cross_zone.enabled=falseInternet Egress: CloudFront as a Cost Lever
Routing egress through CloudFront reduces transfer cost by up to 75 % and improves latency. The math on 50 TB/month:
Direct egress: 50,000 GB × $0.09 = $4,500
Via CloudFront: 50,000 GB × $0.021* = $1,050
Monthly saving: $3,450
Annual saving: $41,400*Blended rate across tiers for US/EU traffic.
resource "aws_cloudfront_distribution" "api" {
enabled = true
origin {
domain_name = aws_lb.api.dns_name
origin_id = "api-alb"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
default_cache_behavior {
target_origin_id = "api-alb"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
compress = true
forwarded_values {
query_string = true
cookies { forward = "none" }
}
min_ttl = 0
default_ttl = 0 # pass-through for API; cache at application level
max_ttl = 86400
}
price_class = "PriceClass_100" # US, Canada, Europe only — cheapest tier
}Transfer Cost Reduction Checklist
Key Takeaways
- Cross-AZ traffic at $0.02/GB round-trip is invisible in architecture diagrams but appears prominently in the bill; audit it before designing microservice communication patterns.
- S3 and DynamoDB Gateway Endpoints are free and eliminate the largest category of NAT Gateway processing charges — this is always the first NAT Gateway optimization to make.
- CloudFront is not just a CDN; it is an egress cost reduction mechanism that cuts internet transfer costs by 50–75 % while improving global latency.
- The NAT Gateway hourly charge is secondary to the per-GB processing charge; reducing data volume through the gateway matters more than reducing the number of gateways.
- Kubernetes topology spread constraints and AZ-aware load balancing are the implementation levers for cross-AZ cost reduction in container workloads.
- Build transfer cost into capacity planning — every architectural decision that increases data movement has a per-byte cost that compounds with scale.