Apr 13, 2026 // aws

AWS CloudFront: Cost, Performance, and the Configuration Mistakes That Cost Both

CloudFront reduces latency and origin load, but misconfigured caching, wrong price classes, and uncompressed responses can make it expensive without the performance benefits.

CloudFront is one of those AWS services where the default configuration works well enough to ship but poorly enough to cost significantly more than it should. Cache hit rates below 20%, all traffic using the most expensive price class, and uncompressed responses doubling data transfer costs — these are common and easy to miss until the bill arrives.

Here’s how CloudFront pricing works, what the common cost and performance mistakes are, and how to fix them.


How CloudFront pricing works

CloudFront charges across three dimensions:

Data transfer out: The cost of serving data from edge locations to end users. Varies by price class and geographic region. For US/Europe: $0.0085/GB for the first 10TB/month, dropping to $0.0060/GB for the next 40TB. Asia Pacific and South America are roughly 2-4x more expensive.

HTTP/HTTPS requests: $0.0075 per 10,000 HTTP requests, $0.0100 per 10,000 HTTPS requests. For high-request-rate applications (APIs, single-page apps with many small assets), this line item can exceed data transfer costs.

Origin fetch: Data transferred from your origin (S3, ALB, EC2) to CloudFront. Often free if the origin is in the same AWS region as the CloudFront distribution — S3 to CloudFront is free. ALB to CloudFront within the same region is also free. Cross-region origin fetches are billed at regional data transfer rates.

Lambda@Edge / CloudFront Functions: Billed per invocation and per execution duration if used.

Real-time logs: Billed per log line if enabled.

The most important cost lever: your cache hit rate. Every cache miss is an origin request (plus the origin serving cost) and every cache hit is just the CloudFront edge serving cost. A distribution with a 90% cache hit rate costs dramatically less per served request than one with a 10% hit rate.


Price classes: stop paying for edge locations you don’t need

CloudFront’s default price class (PriceClass_All) includes edge locations in every region globally — including the most expensive: South America, Middle East, Africa, Asia Pacific.

If your users are primarily in North America and Europe, you’re paying for edge locations in São Paulo, Cape Town, and Mumbai that rarely serve traffic but still contribute to billing.

Price class options:

  • PriceClass_All: All edge locations ($$$)
  • PriceClass_200: US, Canada, Europe, Asia (without the most expensive regions)
  • PriceClass_100: US, Canada, Europe only (cheapest)

For SaaS products with North American and European users: PriceClass_100 typically reduces data transfer costs by 15-25% with no meaningful latency impact for your actual users.

Check your CloudFront access logs or Real-Time Dashboard for actual geographic distribution before changing. If 5% of your traffic is from Asia Pacific and that’s a paid customer segment, PriceClass_100 may degrade their experience unacceptably.


Cache behavior: the performance and cost driver

The cache hit rate depends entirely on your cache behavior configuration. Common mistakes:

Forwarding unnecessary headers to the origin. If you forward the Accept-Language header to your origin, CloudFront caches a separate version of the response for every language. A static JavaScript bundle doesn’t change based on Accept-Language — you’ve just fragmented your cache with no benefit.

CloudFront’s cache key is determined by:

  • The URL (path + query string parameters you specify)
  • The headers you forward
  • The cookies you forward

Every additional header or cookie that varies adds a dimension to the cache key. More cache key dimensions = more cache fragmentation = lower hit rate.

Rule of thumb: Cache key should include only what actually changes the response. For static assets: URL only. For API responses: URL + authentication header (if responses are user-specific) or URL only (if public).

Forwarding the Host header. Required if your origin uses virtual hosting, but forwarding Host is a common default that can break caching when combined with other headers. Use origin request policies carefully.

Managed cache policies. AWS provides pre-built cache policies worth using as a starting point:

  • CachingOptimized: For S3 and static content. 24-hour TTL, no query strings or headers.
  • CachingDisabled: For dynamic content that should always reach the origin.
  • UseOriginCacheControlHeaders: Respects your origin’s Cache-Control headers.

TTL configuration

CloudFront respects Cache-Control: max-age headers from your origin. If your origin sends Cache-Control: no-cache, CloudFront won’t cache anything — every request hits the origin.

For static assets (JS, CSS, images with content-hashed filenames):

Cache-Control: public, max-age=31536000, immutable

One year TTL. Since the filename contains a content hash (e.g., app.a3f7b2.js), changing the content changes the filename, and the old cache entry expires naturally.

For HTML files (index.html, which references the hashed assets):

Cache-Control: public, max-age=0, must-revalidate

Never cached — always fresh. This ensures users get the new asset filenames when you deploy.

For API responses:

Cache-Control: private, no-cache

Or configure CachingDisabled in the CloudFront cache behavior for your API path pattern.

CloudFront distribution-level TTL settings override origin headers if configured. Check your distribution’s minimum, maximum, and default TTL settings aren’t overriding the origin’s Cache-Control headers in ways you didn’t intend.


Compression: enabling it and getting it right

CloudFront supports automatic compression (gzip and Brotli) for responses that aren’t already compressed. Enabling this reduces data transfer costs and improves load times.

Enable it: In your CloudFront cache behavior, set Compress objects automatically to Yes.

Prerequisite: Your origin must serve uncompressed responses, or CloudFront won’t re-compress. If your ALB or EC2 origin is already compressing responses, CloudFront serves them as-is. If your origin is S3 with pre-compressed files, serve them with the correct Content-Encoding header and CloudFront will pass them through.

Common mistake: S3 hosting pre-compressed .gz files but not setting Content-Encoding: gzip on the S3 object metadata. CloudFront serves the compressed bytes without the header, and browsers decompress nothing — serving corrupt-looking content.

Correct S3 metadata for pre-compressed files:

Content-Encoding: gzip
Content-Type: application/javascript  (the original content type, not gzip)

Origin Shield: reducing origin load and cross-region costs

Origin Shield is an additional caching layer between CloudFront edge locations and your origin. When a cache miss occurs at an edge location, it checks Origin Shield before fetching from the origin. This reduces origin request volume and, for origins in a different region from some edge locations, reduces cross-region data transfer.

When it helps:

  • High-traffic distributions with significant origin request volume
  • Origins in a single region serving global CloudFront traffic
  • Origins with high per-request cost (Lambda, expensive API calls)

Cost: $0.009/10,000 HTTP requests through Origin Shield. For a 90% cache hit rate, the additional Origin Shield requests (on the remaining 10%) add minimal cost while reducing origin requests by 50-70%.


Lambda@Edge vs CloudFront Functions

CloudFront supports two edge compute options with very different cost profiles:

CloudFront Functions: Lightweight JavaScript, executes at edge locations, sub-millisecond execution. $0.10 per 1 million invocations. Use for: URL rewrites, header manipulation, simple redirects, A/B testing routing.

Lambda@Edge: Full Lambda runtime (Node.js or Python), executes in regional edge caches, can call external services. $0.60 per 1 million invocations + $0.00005001 per GB-second. Significantly more expensive and slower than CloudFront Functions.

Migration opportunity: If you’re using Lambda@Edge for simple header or URL manipulations that don’t require external API calls, move those to CloudFront Functions. The cost difference is 6x and the latency is better.


Logging and monitoring

CloudFront access logs go to S3. At high traffic volumes, these logs cost meaningfully: $0.50/GB stored in S3 + S3 PUT request costs. For a distribution serving 10TB/month with many small requests, the log volume can be 10-50GB/month — $5-25/month just for log storage.

Options:

  • Sampling: Log a percentage of requests rather than all requests
  • Real-time logs to Kinesis: Pay per delivered log record, route to S3 with lifecycle policies
  • CloudFront default metrics in CloudWatch: Free, limited — hit rate, error rate, request count, origin latency. Sufficient for most monitoring use cases without full access logs.

For cost monitoring, enable CacheStatistics reporting in the CloudFront console. This shows cache hit rate per distribution without requiring log analysis.


Custom error pages and error caching

By default, CloudFront caches error responses (4xx, 5xx) for 5 seconds and then re-attempts the origin. For origins that return sporadic 5xx errors, this means error responses are served briefly and then re-fetched.

Configure custom error caching to control this:

  • Cache 404s for longer (e.g., 24 hours) to reduce origin load from requests for non-existent resources
  • Don’t cache 5xx errors (set minimum TTL to 0) for transient origin issues
  • Custom error pages for better user experience during outages

The quick audit checklist

For a CloudFront cost and performance review:

  1. Check cache hit rate in CloudFront metrics — anything below 70% for static assets is a problem
  2. Check price class — are you paying for edge locations your users don’t use?
  3. Audit cache behaviors — are you forwarding headers/cookies that fragment the cache?
  4. Check TTL configuration — are static assets cached for a year with content hashing?
  5. Check compression — is automatic compression enabled?
  6. Check Origin Shield — is it configured for high-traffic distributions?
  7. Audit Lambda@Edge — can any functions migrate to CloudFront Functions?

A distribution with 95% cache hit rate, PriceClass_100, and compression enabled costs a fraction of one with default settings and poor cache behavior.


Getting help

CloudFront configuration is one of those areas where a few hours of audit work generates meaningful ongoing savings. If your CDN costs are higher than expected or your cache hit rates are low, I can help.


Nick Allevato is an AWS Certified Solutions Architect Professional with 20 years of infrastructure experience. He runs Cold Smoke Consulting, an independent AWS consulting practice.


← all writing