How CloudFront decides what to cache
Amazon CloudFront is a content delivery network. Viewers connect to a nearby edge location, and CloudFront either answers from its cache (a cache hit) or forwards the request to your origin, such as an Amazon S3 bucket, an Application Load Balancer or any HTTP server (a cache miss).

Three pieces of configuration decide what happens to each request:
- Cache behaviors match request paths to settings. A distribution has a default behavior (
*) and can have more, such as/static/*or/api/*. Each behavior points to an origin and carries its own policies. - Cache policies define the cache key (which headers, cookies and query strings make one cached object different from another) and the minimum, default and maximum TTL. They also enable caching of Gzip and Brotli compressed objects.
- Origin request policies define what CloudFront sends to the origin on a miss, without adding it to the cache key.
The split between the last two matters. AWS documents that every value in the cache key is automatically included in origin requests, so the origin request policy is for values the origin needs but that should not fragment the cache, such as a header used only for logging. See Control origin requests with a policy.
Static content: a small cache key and long TTLs
For images, CSS, JavaScript and fonts, the goal is the highest possible cache hit ratio. Keep the cache key small: if the files do not change by cookie or query string, leave cookies and query strings out of it.
The managed CachingOptimized policy does exactly this. It includes no cookies or query strings, only the normalized Accept-Encoding header, and sets a minimum TTL of 1 second, a default of 24 hours and a maximum of 365 days. The full list is in Use managed cache policies.
The safest pattern for static assets is versioned file names, such as app.3f9c2a.js, served with a long Cache-Control lifetime. A new build produces new names, so you never need to purge the old ones.
How Cache-Control and TTLs interact
The TTLs in the cache policy and the headers your origin sends work together. The rules in Manage how long content stays in the cache are, in short:
- If the origin sends
Cache-Control: max-age, CloudFront caches for that value, clamped between the policy’s minimum and maximum TTL. Browsers usemax-ageas sent. - If the origin also sends
s-maxage, CloudFront usess-maxage(again clamped) and browsers still usemax-age. This lets you cache longer at the edge than in the browser. - If the origin sends no
max-age, CloudFront uses the default TTL (or the minimum TTL, if that is greater). - If both
max-ageandExpiresare present, CloudFront usesmax-age. - If the origin sends
no-cache,no-storeorprivateand the minimum TTL is 0, CloudFront respects them. If the minimum TTL is greater than 0, CloudFront caches the response for the minimum TTL anyway.
That last rule is the one that catches teams out. CachingOptimized has a minimum TTL of 1 second, so a personalized response marked private can still be cached for a second and served to another viewer. For any path that returns per-user content, use a cache policy with a minimum TTL of 0.
A typical split looks like this:
# Fingerprinted static asset
Cache-Control: public, max-age=31536000, immutable
# HTML page: short at the edge, revalidated in the browser
Cache-Control: public, max-age=0, s-maxage=300
# Personalized response
Cache-Control: private, no-store
CloudFront also supports stale-while-revalidate and stale-if-error, which let it serve an expired copy while it fetches a fresh one or while the origin is failing, capped by the maximum TTL.
Caching dynamic content safely
Dynamic does not have to mean uncacheable. Many API responses and HTML pages are the same for every viewer for seconds or minutes, and even a short TTL can take most of the load off the origin during a spike.
To do it safely:
- Put dynamic paths in their own cache behavior, such as
/api/*, so their settings do not leak into static paths. - Include in the cache key only the values the response really varies by, for example one query string parameter or a language header. Each extra value multiplies the number of cached copies.
- Never put responses that depend on an
Authorizationheader or session cookie into a shared cache unless that value is part of the cache key. If in doubt, use the managedCachingDisabledpolicy for that behavior and forward what the origin needs with an origin request policy. - If your origin already sets correct
Cache-Controlheaders, the managedUseOriginCacheControlHeaderspolicies (minimum and default TTL 0) let the origin decide per response.
Even with caching disabled, requests still benefit from CloudFront: TLS terminates at an edge location close to the viewer, and CloudFront keeps persistent connections open to the origin.
Compression
CloudFront can compress responses with Gzip or Brotli at the edge. Set Compress objects automatically to Yes on the cache behavior and use a cache policy with Gzip and Brotli enabled; Brotli requires a cache policy rather than legacy cache settings. Per Serve compressed files, CloudFront compresses only listed content types, only objects between 1,000 and 10,000,000 bytes, and only when the origin sends a Content-Length header. If the origin already compressed the response and sends Content-Encoding, CloudFront passes it through unchanged.
Origin Shield
Without it, each regional edge cache can send its own request to your origin for the same object. Origin Shield adds one more caching layer in a Region you choose, ideally the one with the lowest latency to your origin, and consolidates those requests. It helps most with viewers spread across many regions, on-the-fly image processing, live video packaging and origins with limited capacity. AWS notes it is a poor fit for dynamic content that is proxied to the origin or rarely requested. It is charged per request, so estimate the cost first.
Invalidations
An invalidation removes objects from edge caches before they expire:
aws cloudfront create-invalidation \
--distribution-id EDFDVBD6EXAMPLE \
--paths "/index.html" "/css/*"
The first 1,000 invalidation paths per month are free across the account, and a wildcard path counts as one path (Pay for file invalidation). AWS recommends versioned file names over invalidation for files that change often, because invalidation does not clear copies held in browsers or corporate proxies.
Measuring cache hit ratio
You cannot tune what you do not measure:
- Cache hit rate in the CloudFront console. It is one of the additional distribution metrics, which you turn on per distribution for a monthly CloudWatch charge per metric. Origin latency is in the same set.
- Standard logs, where the
x-edge-result-typefield showsHit,RefreshHit,MissorErrorfor each request. Grouping misses by path shows which behaviors need a smaller cache key or a longer TTL.
A low hit ratio usually comes from a cache key that includes values the response does not depend on, such as all cookies or tracking query strings, or from origins that send no-cache on content that could be cached.
Getting help
9Acts is an AWS Advanced Tier Services Partner and has worked with clients on AWS since 2016. If you want a second pair of eyes on your CloudFront setup or your wider AWS bill, see our cloud optimization service.
