CloudFront cache policies: practical tips and trade-offs
A CloudFront distribution can deliver content at sub-100ms latencies from edge locations worldwide, but only if your cache hit ratio is high enough to justify the infrastructure investment. Your cache key—the combination of host, path, headers, query strings, and cookies that CloudFront uses to identify unique objects—directly controls whether requests hit the cache or miss and travel all the way to your origin. We'll walk through the practical decisions that shape your cache policies, what metrics matter, and the trade-offs you'll face when tuning them for real production workloads.
Understanding the cache key and hit ratio
CloudFront's cache key determines whether two requests are considered identical. If your cache key includes every header and query parameter, you'll fragment your cache: a request for /api/users?sort=asc&page=1×tamp=1234567890 is cached separately from the same endpoint with timestamp=1234567891, even though the response is identical. This hammers your origin and defeats the purpose of edge caching.
Cache hit ratio is the percentage of requests served from edge locations without reaching your origin. A typical target is 80–95% for static assets and 40–70% for dynamic content, depending on your workload. We've seen production incidents where a cache key configuration change dropped hit ratio from 85% to 22% overnight, quadrupling origin load.
You control this with two CloudFront policies:
- Cache policy: defines what goes into the cache key (headers, query strings, cookies) and how long objects live in cache.
- Origin request policy: defines what headers are forwarded to your origin, separate from the cache key.
This separation is crucial. You might need to forward the Authorization header to your origin for authentication, but you don't want it in the cache key, or every unique bearer token creates a new cache entry.
Include only headers and query strings that change content
The most common mistake is adding every header to your cache key. If you're caching a response that varies by Accept-Language, include it. If you're caching a static image that doesn't, don't.
For example, assume you serve a product page at GET /products/12345. The response changes based on:
- The product ID in the path (always in cache key).
- The user's preferred language (varies content, include in cache key).
- The CDN edge location (does not vary content, exclude).
- The origin server's internal load balancer (does not vary content, exclude).
A typical cache policy for this scenario includes Accept-Language in the cache key and omits headers like User-Agent, X-Forwarded-For, and CloudFront-Viewer-Country.
Query strings are trickier. If your product page supports filtering—/products?category=shoes&sort=price—and the response changes based on these parameters, include them. But if you have tracking query strings like ?utm_source=email&utm_campaign=summer that your application ignores, exclude them from the cache key or you'll fragment the cache across thousands of marketing variants.
A practical approach:
- Identify which query parameters affect the response. Use your application logs or request tracing.
- Create a whitelist of cache-relevant parameters.
- Configure CloudFront to include only those in the cache key.
In the AWS Console, this looks like:
Cache Policy: Custom
Query Strings Behavior: Whitelist
Query Strings List: category, sort, pageEverything else—utm_*, fbclid, session tracking—stays out of the cache key but can still reach your origin via the origin request policy if needed.
Separate cache and origin request policies
Many teams conflate cache policy and origin request policy, thinking they're the same configuration. They're not, and the distinction is powerful.
Cache policy defines the cache key and TTL. Origin request policy defines which headers and query strings are forwarded to your origin, after the cache lookup fails.
Here's a real scenario: you're serving a REST API behind CloudFront. Your origin needs the Authorization header to authenticate requests and the X-Request-ID header for request tracking. But you don't want these in the cache key.
- Cache policy: don't include
AuthorizationorX-Request-IDin the cache key. - Origin request policy: forward both headers to origin on cache misses.
This way, a cache hit returns the same response regardless of the bearer token or request ID, but on a miss, your origin still receives the full headers it needs.
Another example: your application runs behind a WAF and needs the CloudFront-Viewer-Country header for geo-routing logic on the origin. Include it in the origin request policy but not the cache policy. The edge will serve cached responses to viewers from different countries, reducing origin load.
To set this in Terraform:
resource "aws_cloudfront_cache_policy" "api_cache" {
name = "api-cache-policy"
parameters_in_cache_key_and_forwarded_to_origin {
headers_config {
header_behavior = "none"
}
query_strings_config {
query_string_behavior = "whitelist"
query_strings {
items = ["api_key", "format"]
}
}
cookies_config {
cookie_behavior = "none"
}
}
default_ttl = 300
max_ttl = 3600
min_ttl = 0
}
resource "aws_cloudfront_origin_request_policy" "api_origin" {
name = "api-origin-policy"
headers_config {
header_behavior = "whitelist"
headers {
items = ["Authorization", "X-Request-ID"]
}
}
query_strings_config {
query_string_behavior = "all"
}
cookies_config {
cookie_behavior = "all"
}
}Notice how the cache policy has minimal headers and a specific query string whitelist, while the origin request policy is more permissive. This maximizes cache hits while ensuring your origin gets the context it needs.
Long TTLs for hashed assets
Static assets like JavaScript bundles, CSS, and images are often versioned with content hashes: bundle.a1b2c3d4.js. If the content changes, the filename changes, so you can safely use a very long TTL—30 days, 90 days, or even indefinitely.
This is a high-confidence cache scenario:
- The URL is unique per content version.
- The response never changes during the asset's lifetime.
- Cache misses always fetch the correct version from origin.
For hashed assets, we recommend:
Cache Policy: Managed-CachingOptimized
TTL: 31536000 seconds (1 year) or higherFor non-hashed assets like index.html, use a shorter TTL—300 to 3600 seconds. If you deploy a new version, edges will refresh within your TTL window.
In practice, you often need two distributions or behaviors:
/static/*(hashed): long TTL, aggressive caching./(index.html): short TTL, more frequent origin checks.
CloudFront behaviors let you apply different cache policies to different path patterns, so you can tune each independently.
Monitor cache hit ratio
You can't optimize what you don't measure. CloudFront exports cache statistics to CloudWatch: CacheHitRate, Requests, BytesDownloaded, and BytesUploaded.
Set up a CloudWatch dashboard to visualize these metrics in real time. A sudden drop in CacheHitRate often signals a configuration change, a new query parameter from a marketing campaign, or a cache invalidation.
Create an alarm:
Metric: CacheHitRate
Statistic: Average
Threshold: < 70% for 5 minutes
Action: SNS notificationWe've found that cache hit ratio correlates strongly with origin latency. When hit ratio drops, origin response times spike, and downstream services feel the strain. Early alerts help you catch and fix cache misconfigurations before they cascade.
Use CloudFront logs to investigate low hit ratio. Enable detailed logging and query the logs in Athena or your log aggregator. Look for:
- Repeated requests with different query strings that should be identical.
- Cache status codes:
Missvs.Hitvs.RefreshHit. - Response status codes that might not be cacheable (5xx errors, 307/308 redirects).
Common mistakes
Including all headers in the cache key: This fragments the cache instantly. Review AWS managed policies and start minimal, adding headers only when the content actually varies.
Forgetting about cookies: If your application uses cookies but you don't intentionally exclude them from the cache key, CloudFront treats every unique cookie value as a separate cache entry. Disable cookie caching unless you explicitly need it.
Not separating cache and origin request policies: Many teams create overly restrictive origin request policies, afraid that missing headers will break the origin. Use separate policies to forward context without fragmenting the cache.
Setting TTLs too short for static assets: A 1-hour TTL on a hashed JavaScript file means 24 redundant origin fetches per day, even though the content never changes. Push TTLs longer for immutable content.
Ignoring cache hit ratio monitoring: Teams deploy cache policies and never check whether they're working. Without metrics, you're flying blind.
Key takeaways
- Cache key design directly controls hit ratio. Include only headers and query strings that change the response.
- Separate cache policies from origin request policies. Forward context to your origin without fragmenting the cache.
- Use long TTLs for hashed assets and shorter TTLs for dynamic or versioned content like index.html.
- Monitor cache hit ratio in CloudWatch. A sudden drop signals a configuration problem or a change in traffic patterns.
- Start with AWS managed policies and customize only the cache key headers and query strings that matter to your application.
- Review CloudFront logs to identify cache misses. Often, marketing query strings or internal request IDs are fragmenting the cache unnecessarily.






Comments
Loading comments…