BlogCloud & Infrastructure
Cloud & Infrastructure

CloudFront Cost Optimization: CDN Configuration That Saves Thousands

CloudFront is cheap compared to S3 direct egress, but misconfigured distributions waste money through unnecessary origin fetches, wrong price classes, missing compression, and poor cache policies. This guide covers every CloudFront cost lever with real configuration examples.

P

Priya Sharma

Full-Stack Developer and open-source contributor with a passion for performance and developer experience.

April 5, 2026
19 min read

CloudFront Pricing Breakdown

CloudFront is dramatically cheaper than serving traffic directly from EC2 or S3. But "cheap" does not mean "free", and CloudFront bills accumulate through several mechanisms that are easy to optimize.

CloudFront Cost Components:

1. Data Transfer Out (to internet):
   PriceClass_100 (US/Europe/Israel):
     First 10TB:   $0.0085/GB
     Next 40TB:    $0.0080/GB
     Next 100TB:   $0.0060/GB
   PriceClass_200 (+ Asia Pacific, Middle East, Africa):
     First 10TB:   $0.0120/GB (some regions up to $0.0170/GB)
   PriceClass_All (all regions):
     Most expensive — South America: $0.025/GB, India: $0.017/GB

2. HTTP/HTTPS Requests:
   $0.0100 per 10,000 HTTPS requests
   $0.0075 per 10,000 HTTP requests
   
3. Origin Fetch (CloudFront to your origin):
   EC2/ALB: $0.020/GB
   S3 in same region: FREE
   
4. Lambda@Edge:
   $0.60 per 1M requests + $0.00005001 per GB-second

Key insight: A 1MB image served to 1M users/month:
  Direct S3 egress:  1TB Ă— $0.09 = $92.16
  Via CloudFront:    1TB Ă— $0.0085 = $8.70 (91% cheaper!)
  Origin fetch (S3): FREE (same region)

Price Class Selection

resource "aws_cloudfront_distribution" "main" {
  # WRONG: Using PriceClass_All for a US-only business
  # price_class = "PriceClass_All"  # Expensive, Australia/Brazil/India edge nodes

  # RIGHT: Match price class to actual user geography
  price_class = "PriceClass_100"  # US, Canada, Europe, Israel only
  # Saves: 30-40% on transfer costs vs PriceClass_All

  # For global audience:
  # price_class = "PriceClass_200"  # Adds Asia Pacific + Middle East
  # price_class = "PriceClass_All"  # Only if South America/Africa matter

  # How to decide:
  # 1. Check CloudFront Access Logs for viewer country distribution
  # 2. If 95% of traffic is US/Europe: PriceClass_100
  # 3. If significant Asia/Pacific: PriceClass_200
  # 4. PriceClass_All only if global enterprise with regional SLAs
}

Cache Hit Rate Optimization

The single most important CloudFront metric: Cache Hit Rate
Goal: 90%+ cache hit rate

Low cache hit rate means:
  - More origin fetches (costs $0.020/GB instead of serving from edge)
  - Higher latency for users
  - More load on your origin servers

What kills your cache hit rate:

1. Query strings creating unique cache keys unnecessarily
   /api/products?sort=price&page=1  -- different cache key than
   /api/products?page=1&sort=price  -- same content, different order!

2. Cookies being forwarded to origin (disables caching for that path)
   session_id cookie = unique per user = 0% cache hit rate

3. Authorization headers vary the cache
   Each user's token = unique cache entry = effectively no caching

4. Volatile URL parameters (tracking: ?utm_source=google)
   utm_source, fbclid, gclid should be stripped or ignored in cache key
# Managed Cache Policies (use these instead of custom)
# CachingOptimized: best for S3 static assets
# CachingDisabled: for API endpoints that must not be cached
# CachingOptimizedForUncompressedObjects: when compression is handled elsewhere

# Custom cache policy for static assets with query string handling
resource "aws_cloudfront_cache_policy" "static_assets" {
  name        = "static-assets-policy"
  min_ttl     = 86400     # 1 day minimum
  default_ttl = 604800    # 7 days default
  max_ttl     = 31536000  # 1 year maximum

  parameters_in_cache_key_and_forwarded_to_origin {
    cookies_config {
      cookie_behavior = "none"  # Don't include cookies in cache key
    }
    headers_config {
      header_behavior = "none"  # Don't include headers in cache key
    }
    query_strings_config {
      query_string_behavior = "whitelist"
      query_strings {
        items = ["v", "version"]  # Only cache-bust version param
      }
    }
    enable_accept_encoding_gzip   = true  # CRITICAL: enables compression
    enable_accept_encoding_brotli = true  # Brotli = smaller than gzip
  }
}

# Cache policy for authenticated API endpoints (must not cache)
resource "aws_cloudfront_cache_policy" "api_nocache" {
  name        = "api-nocache"
  min_ttl     = 0
  default_ttl = 0
  max_ttl     = 0

  parameters_in_cache_key_and_forwarded_to_origin {
    cookies_config    { cookie_behavior = "none" }
    headers_config    { header_behavior = "none" }
    query_strings_config { query_string_behavior = "none" }
  }
}

# Origin Request Policy — what to forward to origin (separate from cache key)
resource "aws_cloudfront_origin_request_policy" "api" {
  name = "api-origin-request"

  cookies_config {
    cookie_behavior = "all"  # Forward all cookies to origin (but don't cache on them)
  }
  headers_config {
    header_behavior = "whitelist"
    headers {
      items = ["Authorization", "Accept", "Content-Type"]
    }
  }
  query_strings_config {
    query_string_behavior = "all"  # Forward all QS to origin
  }
}

Compression: Reduce Transfer Costs 60-80%

CloudFront supports automatic compression for text-based content:
  HTML, CSS, JavaScript, JSON, XML, SVG, fonts

Without compression:
  1MB JavaScript file Ă— 10M requests Ă— $0.0085/GB = $85/month
  
With gzip compression (typical 70% reduction):
  300KB Ă— 10M requests Ă— $0.0085/GB = $25.50/month
  Saving: $59.50/month per asset per 10M requests

With Brotli (typical 80% reduction vs uncompressed, 15% better than gzip):
  200KB Ă— 10M requests Ă— $0.0085/GB = $17/month
  Saving: $68/month per asset

Enabling compression in CloudFront:
  1. Set compress = true in cache behavior (Terraform)
  2. Ensure origin does NOT double-compress (CloudFront compresses edge-side)
  3. Set enable_accept_encoding_gzip = true in cache policy
  4. Set enable_accept_encoding_brotli = true for Brotli support

Pre-compress at build time + serve with Content-Encoding header:
  Even better: pre-gzip/brotli assets, upload to S3 with Content-Encoding header
  CloudFront serves pre-compressed file directly — no CPU overhead

Caching Headers from Your Origin

# FastAPI: Set cache headers to control CloudFront TTL
from fastapi import FastAPI, Response
from fastapi.responses import JSONResponse

app = FastAPI()

@app.get("/api/products")
async def get_products(response: Response):
    products = await fetch_products()
    
    # Tell CloudFront: cache this for 5 minutes
    response.headers["Cache-Control"] = "public, max-age=300, s-maxage=300"
    response.headers["Vary"] = "Accept-Encoding"  # Vary on compression
    return products

@app.get("/api/products/{id}/inventory")
async def get_inventory(id: int, response: Response):
    # Real-time data — do not cache
    response.headers["Cache-Control"] = "no-cache, no-store"
    return await fetch_inventory(id)

@app.get("/static/config.json")
async def get_config(response: Response):
    # Immutable config — cache for 7 days
    response.headers["Cache-Control"] = "public, max-age=604800, immutable"
    return await get_app_config()

# Cache-Control reference:
# public, max-age=N      = CDN and browser cache for N seconds
# s-maxage=N             = CDN caches for N seconds (overrides max-age for CDN)
# private, max-age=N     = Browser only, CDN does NOT cache
# no-cache               = Revalidate on every request
# no-store               = Never cache anywhere
# immutable              = Never re-fetch (use only with content-hashed URLs)
# stale-while-revalidate = Serve stale while fetching fresh in background

CloudFront Access Logs for Cost Analysis

# Enable standard logging (costs $0.0009/10K requests in S3)
# Use for periodic analysis, not always-on

# Analyze cache hit rate from logs using Athena
# CREATE EXTERNAL TABLE cloudfront_logs (
#   date DATE, time STRING, location STRING, bytes BIGINT,
#   ip STRING, method STRING, host STRING, uri STRING,
#   status INT, referer STRING, useragent STRING,
#   uriquery STRING, cookie STRING, resulttype STRING, ...
# )
# PARTITIONED BY (year STRING, month STRING, day STRING)
# ROW FORMAT DELIMITED FIELDS TERMINATED BY '	'
# LOCATION 's3://my-cf-logs/';

# Cache hit rate query:
# SELECT
#   resulttype,
#   COUNT(*) as requests,
#   ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER(), 2) as pct
# FROM cloudfront_logs
# WHERE year='2026' AND month='03'
# GROUP BY resulttype;

# resulttype values:
# Hit       = served from edge cache (good!)
# Miss      = origin fetch required (costly)
# RefreshHit = conditional GET revalidation
# LambdaGeneratedResponse = Lambda@Edge response

# Target: Hit% > 90%, Miss% < 10%

Real-World Optimization Results

Before optimization (50TB/month traffic):
  PriceClass_All:          $2,500/month transfer
  No compression:          $2,500/month (uncompressed assets)
  Cache hit rate: 55%      Many unnecessary origin fetches
  Origin fetch:            $650/month (30TB to origin)
  Total CloudFront:        $5,650/month

After optimization:
  PriceClass_100 (US/EU):  $850/month (traffic profile was 95% US/EU)
  Brotli compression 80%:  $510/month transfer
  Cache hit rate: 94%:     Origin fetch reduced by 70%
  Origin fetch (S3 free):  $0 (moved origin to S3 same region)
  Total CloudFront:        $1,360/month

Savings: $4,290/month (76% reduction)
Key changes:
  1. Price class change: saved $1,650/month instantly
  2. Compression: saved $1,990/month
  3. Cache hit rate fix: saved $455/month in origin fetch
  4. Origin to S3 (free origin fetch): saved $650/month

Conclusion

CloudFront cost optimization is dominated by three levers: the right price class for your audience geography, maximum compression for text assets, and a high cache hit rate through sensible cache policies. Each lever independently delivers 20-40% savings, and together they can reduce CloudFront costs by 60-80% compared to a default configuration.

P

Priya Sharma

Full-Stack Developer and open-source contributor with a passion for performance and developer experience.

Ready to Transform Your Infrastructure?

Let's discuss how we can help you achieve similar results.