The Egress Fee Problem
Ingress (data coming into the cloud) is free. Egress (data leaving the cloud) costs $0.09/GB on AWS. This asymmetry is intentional β cloud providers profit from keeping your data inside their network and charge you to move it out. When analysts call cloud egress fees "the modern data tax," they are not being hyperbolic.
At 100TB/month, egress alone costs $9,216. At 1PB/month (not unusual for video, gaming, or data platform companies), that is $92,160/month just in data transfer fees β before you pay for compute, storage, or any actual services. The Bandwidth Alliance, a coalition of cloud providers and CDNs, has been pushing for egress fee reform, but meaningful change has been slow.
AWS Egress Pricing Breakdown
AWS Data Transfer OUT to Internet (us-east-1):
First 100GB/month: FREE
Next 9.9TB (up to 10TB): $0.09/GB = $921.60 for 10TB
Next 40TB (up to 50TB): $0.085/GB = $3,400 for 40TB
Next 100TB (up to 150TB): $0.07/GB = $7,000 for 100TB
Over 150TB: $0.05/GB
AWS Data Transfer within same region:
Same AZ (EC2 to EC2): FREE
Different AZ: $0.01/GB EACH DIRECTION = $0.02/GB round trip
To CloudFront (origin): FREE (CloudFront charges for edge delivery)
To S3 (same region): FREE
EC2 to ECR (same region): FREE if using VPC endpoint
AWS cross-region transfer:
EC2 to EC2 different region: $0.02/GB
S3 cross-region replication: $0.02/GB (plus S3 replication fee)
CloudFront pricing (edge delivery):
First 10TB/month: $0.0085/GB (94% cheaper than direct EC2 egress!)
Next 40TB: $0.0080/GB
Next 100TB: $0.0060/GB
Over 5PB: $0.0025/GB
CloudFront vs Direct EC2 at 100TB:
Direct: $7,700
CloudFront: $770
Savings: $6,930 (90%)
Where Hidden Egress Costs Hide
# Cross-AZ traffic is the most commonly overlooked cost
# Every microservice call that crosses AZs = $0.02/GB
# Example: Load-balanced API calls
# ALB distributes traffic across 3 AZs
# Each request crosses AZ boundary: ~50KB payload = $0.000001 per request
# At 100M requests/day: $100/day = $3,000/month just for cross-AZ API traffic!
# Fix: Use topology-aware routing in Kubernetes
# Service TopologyAwareHints tells kube-proxy to prefer same-AZ pods
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
service.kubernetes.io/topology-mode: Auto # Prefer same-zone endpoints
spec:
selector:
app: my-app
ports:
- port: 80
# NAT Gateway hidden cost:
# Each AZ should have its own NAT Gateway for HA
# But all outbound traffic to internet flows through NAT: $0.045/GB
# At 10TB/month: $460/month just for NAT processing (plus gateway fee)
# Find your NAT Gateway costs
aws ce get-cost-and-usage --time-period Start=2026-02-01,End=2026-03-01 --granularity MONTHLY --filter '{"Dimensions": {"Key": "SERVICE", "Values": ["Amazon Virtual Private Cloud"]}}' --metrics BlendedCost --output table
Strategy 1: CloudFront for All User-Facing Traffic
resource "aws_cloudfront_distribution" "main" {
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
price_class = "PriceClass_100" # US/Europe only = lower cost
origin {
domain_name = aws_alb.main.dns_name
origin_id = "ALB-origin"
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 = "ALB-origin"
viewer_protocol_policy = "redirect-to-https"
cache_policy_id = aws_cloudfront_cache_policy.optimized.id
origin_request_policy_id = data.aws_cloudfront_origin_request_policy.all_viewer.id
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
compress = true # Gzip/Brotli reduces transfer size 60-80%
}
# Cache API responses that are safe to cache
ordered_cache_behavior {
path_pattern = "/api/v1/products*"
target_origin_id = "ALB-origin"
viewer_protocol_policy = "https-only"
cache_policy_id = aws_cloudfront_cache_policy.api_cache.id
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
}
}
resource "aws_cloudfront_cache_policy" "api_cache" {
name = "api-cache-5min"
min_ttl = 0
default_ttl = 300 # 5 minutes default cache
max_ttl = 3600
parameters_in_cache_key_and_forwarded_to_origin {
cookies_config { cookie_behavior = "none" }
headers_config { header_behavior = "none" }
query_strings_config {
query_string_behavior = "whitelist"
query_strings { items = ["page", "limit", "sort"] }
}
enable_accept_encoding_gzip = true
enable_accept_encoding_brotli = true
}
}
Strategy 2: VPC Endpoints β Eliminate NAT for AWS Services
# Traffic to AWS services (S3, DynamoDB, SQS, etc.) goes through NAT by default
# VPC endpoints route traffic within AWS network β no NAT, no egress charge
# Calculate potential NAT Gateway savings first
# Find all NAT Gateway charges
aws cloudwatch get-metric-statistics --namespace AWS/NATGateway --metric-name BytesOutToDestination --dimensions Name=NatGatewayId,Value=nat-xxxxxxxxx --start-time 2026-02-01T00:00:00Z --end-time 2026-03-01T00:00:00Z --period 2592000 --statistics Sum
# Create gateway endpoints (FREE for S3 and DynamoDB)
aws ec2 create-vpc-endpoint --vpc-id vpc-xxxxx --service-name com.amazonaws.us-east-1.s3 --route-table-ids rtb-xxxxx rtb-yyyyy
aws ec2 create-vpc-endpoint --vpc-id vpc-xxxxx --service-name com.amazonaws.us-east-1.dynamodb --route-table-ids rtb-xxxxx rtb-yyyyy
# Interface endpoints for other services (charged at $0.01/hr + $0.01/GB β but cheaper than NAT)
aws ec2 create-vpc-endpoint --vpc-endpoint-type Interface --vpc-id vpc-xxxxx --service-name com.amazonaws.us-east-1.sqs --subnet-ids subnet-xxxxx --security-group-ids sg-xxxxx
Strategy 3: S3 Transfer Acceleration Only When Needed
# S3 Transfer Acceleration: speeds up uploads from distant clients
# Cost: $0.04-0.08/GB ON TOP of normal S3 egress
# Only use it if you have proved global latency problems
# Test if Transfer Acceleration actually helps for your use case
aws s3api create-bucket --bucket perf-test-bucket --region us-east-1
aws s3api put-bucket-accelerate-configuration --bucket perf-test-bucket --accelerate-configuration Status=Enabled
# Compare speeds
time aws s3 cp large-file.bin s3://perf-test-bucket/ # Without acceleration
time aws s3 cp large-file.bin s3://perf-test-bucket/ --endpoint-url https://perf-test-bucket.s3-accelerate.amazonaws.com # With
# If Transfer Acceleration only gives 10-20% improvement, cost rarely justifies it
# If improvement is 2-3x for globally distributed users: may be worth it
# Alternative: CloudFront with S3 origin
# Uploads via CloudFront's edge network to S3 β free acceleration-like behavior
# And CloudFront charges less than S3 Transfer Acceleration
Strategy 4: Compress Everything
# Nginx: enable gzip compression β reduces text/JSON transfer by 60-80%
# Less data transferred = less egress cost
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/xml+rss
application/vnd.ms-fontobject
application/x-font-ttf
font/opentype
image/svg+xml
image/x-icon;
# Brotli (better compression than gzip, supported by all modern browsers)
# Requires nginx-module-brotli
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/javascript;
# For API responses: if you return JSON with lots of repeated keys,
# 100KB response compresses to ~15KB = 85% egress reduction
Strategy 5: Multi-Region Architecture β Use CloudFront Regional Edge
Pattern: Serve users from the nearest CloudFront edge, not your origin
Without CloudFront:
User in Tokyo requests US-East-1 API:
- 180ms latency
- $0.09/GB egress from EC2 in us-east-1
- Total for 1TB Tokyo traffic: $92
With CloudFront edge + Lambda@Edge or CloudFront Functions:
- Dynamic requests: 60ms latency (served from Tokyo edge)
- $0.0085/GB CloudFront pricing (Tokyo edge)
- Total for 1TB Tokyo traffic: $8.70
- Savings: $83.30/TB
CloudFront price classes to control cost:
PriceClass_All: All edge locations worldwide (most expensive)
PriceClass_200: US, Canada, Europe, Asia, Africa, Middle East
PriceClass_100: US, Canada, Europe only (cheapest)
For most SaaS apps: PriceClass_200 covers 95% of users at lower cost than PriceClass_All.
For US/EU focused apps: PriceClass_100 reduces CloudFront costs 30-40%.
Strategy 6: Multi-Cloud Egress Arbitrage
Cloudflare R2 β zero egress fees
S3-compatible object storage
Price: $0.015/GB storage (vs $0.023 for S3)
Egress: FREE (to internet)
Use case: Store assets, videos, user uploads in R2
Serve directly from R2 or via Cloudflare CDN
Migration from S3:
aws s3 sync s3://my-bucket r2://my-bucket --endpoint-url https://account.r2.cloudflarestorage.com
Savings example: 1PB/month egress
AWS S3: $92,000/month egress
Cloudflare R2: $0 egress
Storage: $15,000 (R2) vs $23,000 (S3)
Total savings: $100,000/month!
Backblaze B2 β $0.01/GB egress (90% cheaper than AWS)
But: no free Cloudflare CDN integration (Cloudflare is a B2 partner β CDN is free)
Use for: backups, cold storage, media delivery
DigitalOcean Spaces:
$0.01/GB egress, S3-compatible
First 1TB free with paid plan
Simpler than R2 setup
When to use multi-cloud for storage:
- Public assets (images, videos, downloads) = R2 or Backblaze B2
- Private data = keep in AWS for latency and security
- Backups = cheapest option (Glacier Deep Archive or B2)
Strategy 7: Direct Connect for High-Volume Hybrid
AWS Direct Connect pricing:
Port fee: $0.025-0.30/hr depending on capacity (1G or 10G)
Data transfer via Direct Connect: $0.02/GB (vs $0.09/GB internet egress)
For 100TB/month:
Internet egress: $7,700
Direct Connect (1G port): $18/day + $2,048 data = $2,066/month
Savings: $5,634/month β pays for itself above ~50TB/month
Use cases:
- Hybrid cloud with significant on-premises β cloud traffic
- Financial services regulatory requirements
- Consistent low-latency connections
- Large data movement (backups, replication, batch ETL)
Egress Cost Calculator
def calculate_monthly_egress_cost(gb_per_month: float, provider: str = "aws") -> dict:
"""Calculate egress costs and show optimization potential."""
costs = {
"aws": [
(100/1024, 0), # First 100GB free
(10 * 1024, 0.09), # Next 10TB
(50 * 1024, 0.085), # Next 40TB
(150 * 1024, 0.07), # Next 100TB
(float('inf'), 0.05), # Over 150TB
],
"gcp_standard": [
(1 * 1024, 0.085), # First 1TB
(10 * 1024, 0.07),
(float('inf'), 0.06),
],
"azure": [
(5 * 1024, 0.087),
(30 * 1024, 0.083),
(float('inf'), 0.07),
],
"cloudfront": [
(10 * 1024, 0.0085), # First 10TB
(50 * 1024, 0.008),
(150 * 1024, 0.006),
(float('inf'), 0.004),
]
}
def calc_cost(tiers, gb):
total = 0
remaining = gb
prev = 0
for limit, rate in tiers:
if remaining <= 0:
break
tier_gb = min(remaining, limit - prev)
total += tier_gb * rate
remaining -= tier_gb
prev = limit
return total
direct_cost = calc_cost(costs[provider], gb_per_month)
cf_cost = calc_cost(costs["cloudfront"], gb_per_month) if provider == "aws" else 0
return {
"provider": provider,
"monthly_gb": gb_per_month,
"direct_egress_cost": round(direct_cost, 2),
"cloudfront_cost": round(cf_cost, 2),
"cloudfront_savings": round(direct_cost - cf_cost, 2),
"r2_cost": 0, # Cloudflare R2 = free egress
"r2_savings": round(direct_cost, 2),
}
# Example
print(calculate_monthly_egress_cost(100 * 1024)) # 100TB
# Output:
# direct_egress_cost: 7,700
# cloudfront_cost: 770
# cloudfront_savings: 6,930
# r2_savings: 7,700
Conclusion
Egress fees are a tax on your success β the more users you serve, the more you pay. But most of that cost is avoidable. CDNs reduce origin fetch by 90%+. VPC endpoints eliminate NAT Gateway processing. Cloudflare R2 eliminates storage egress entirely. Compression reduces payload sizes 60-80%.
For most applications, the combination of CloudFront for user-facing traffic + VPC endpoints for AWS service traffic + Cloudflare R2 for public static assets reduces total egress costs by 70-90%. At 100TB/month, that is $7,000/month in savings for a few hours of configuration work.
Alex Thompson
CEO & Cloud Architecture Expert at ZeonEdge with 15+ years building enterprise infrastructure.