Origins, Behaviors & Caching
A CloudFront distribution is a global content delivery network (CDN — a fleet of edge servers near your users that cache copies of your content). But a CDN is only fast if it actually serves cached copies instead of phoning home to your server every time. Three knobs control this: origins (where your content really lives), cache behaviors (which origin handles which URL path), and cache policies (how CloudFront decides whether two requests are “the same”). Getting the cache key right is the single biggest lever on how effective your CDN is, so this page focuses there.
Origins
An origin is the source of truth that CloudFront pulls content from when it does not already have a cached copy. A distribution can have one origin or many. Common origins:
- An Amazon S3 bucket (Simple Storage Service — object storage) for static files like images, CSS, and JavaScript.
- An Application Load Balancer (ALB) or an EC2 instance (Elastic Compute Cloud — a virtual server) for a dynamic application.
- Any public HTTP server, even one outside AWS (a “custom origin”).
CloudFront fetches from the origin only on a cache miss (when no fresh copy is at the edge). Every other request is served from the edge — that is the whole point.
Cache behaviors
A cache behavior maps a URL path pattern to a specific origin, plus the caching rules for that path. Every distribution has one default behavior (path pattern *) that catches everything not matched by a more specific rule. You then add ordered behaviors that are evaluated top to bottom; the first matching pattern wins.
This lets you route different paths to different origins from one domain. A classic setup:
| Path pattern | Origin | Why |
|---|---|---|
/static/* | S3 bucket | Static assets, cache hard |
/api/* | ALB / API origin | Dynamic responses, cache little or nothing |
* (default) | ALB / web origin | Everything else |
When to use multiple behaviors: when one domain serves both heavily cacheable static files and barely-cacheable dynamic API responses. Use separate behaviors so you can cache /static/* aggressively while leaving /api/* nearly uncached. When not to: if everything behaves the same, one default behavior is simpler.
Cache policies, origin request policies, and TTLs
Two policies control what CloudFront sends and what it remembers:
- Cache policy — defines the cache key (which parts of the request make a response “unique”) and the TTLs (Time To Live — how long a cached copy stays fresh before CloudFront re-checks the origin). The cache key can include selected headers, query strings, and cookies.
- Origin request policy — defines what CloudFront forwards to the origin. This is separate on purpose: you can forward a header to your app for logging without it becoming part of the cache key.
AWS provides managed cache policies so you rarely start from scratch:
| Managed cache policy | Caches based on | Use for |
|---|---|---|
CachingOptimized | URL path only (no headers/cookies/query strings) | Static assets — best hit ratio |
CachingDisabled | Nothing is cached | APIs, fully dynamic content |
CachingOptimizedForUncompressedObjects | URL path, no compression negotiation | Already-compressed media |
TTLs have three values: Minimum, Maximum, and Default. If your origin sends a Cache-Control: max-age=... header, CloudFront honors it within the min/max bounds. If the origin sends nothing, the Default TTL applies.
The cache key gotcha (the big one)
The cache key is what makes or breaks your hit ratio. Every distinct cache key value is a separate cached object. If your cache key includes a header, query string, or cookie that varies a lot but does not actually change the response, you “shatter” the cache: the same content gets stored thousands of times under different keys, and most requests become cache misses.
Examples of cache-shattering keys:
- A
session-idcookie in the key → one cached copy per user. - A
utm_sourcemarketing query string in the key → one copy per ad campaign, for identical HTML. - A
User-Agentheader in the key → one copy per browser version.
Warning: Putting unnecessary headers, cookies, or query strings in the cache key is the most common reason a CloudFront hit ratio is terrible. Tune the cache key to include only the things that genuinely change the response (for example,
Accept-Encodingfor gzip vs Brotli, or a?lang=frparameter that returns translated content). When in doubt, exclude it.
How to: create a cache policy
Console steps
- Open the CloudFront console and choose Policies in the left menu, then the Cache tab.
- Click Create cache policy.
- Set a Name, e.g.
static-assets-cache. - Under TTL settings, set Minimum
1, Default86400(1 day), Maximum31536000(1 year). - Under Cache key settings:
- Headers: choose None (or include only
Accept-Encoding). - Query strings: None.
- Cookies: None.
- Headers: choose None (or include only
- Click Create, then attach it to a behavior in your distribution.
AWS CLI
aws cloudfront create-cache-policy --cache-policy-config '{
"Name": "static-assets-cache",
"DefaultTTL": 86400,
"MaxTTL": 31536000,
"MinTTL": 1,
"ParametersInCacheKeyAndForwardedToOrigin": {
"EnableAcceptEncodingGzip": true,
"EnableAcceptEncodingBrotli": true,
"HeadersConfig": { "HeaderBehavior": "none" },
"QueryStringsConfig": { "QueryStringBehavior": "none" },
"CookiesConfig": { "CookieBehavior": "none" }
}
}'
Output:
{
"Location": "https://cloudfront.amazonaws.com/2020-05-31/cache-policy/a1b2c3d4-1111-2222-3333-444455556666",
"ETag": "E2QWRUHEXAMPLE",
"CachePolicy": {
"Id": "a1b2c3d4-1111-2222-3333-444455556666",
"CachePolicyConfig": { "Name": "static-assets-cache", "DefaultTTL": 86400 }
}
}
You can check whether requests are hitting the cache by reading the x-cache response header:
curl -sI https://d123abcdef.cloudfront.net/static/logo.png | grep -i x-cache
Output:
x-cache: Hit from cloudfront
Hit from cloudfront means it was served from the edge; Miss from cloudfront means CloudFront fetched from the origin.
Attaching a policy to a behavior (Terraform)
resource "aws_cloudfront_distribution" "site" {
# ... origins and other settings ...
ordered_cache_behavior {
path_pattern = "/static/*"
target_origin_id = "s3-assets"
viewer_protocol_policy = "redirect-to-https"
cache_policy_id = aws_cloudfront_cache_policy.static.id
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
}
}
Cost note
CloudFront charges for data transfer out to the internet and per 10,000 requests, but cache hits never touch your origin, so a high hit ratio cuts both origin compute/transfer costs and origin load. Cache policies, behaviors, and TTLs themselves are free — tuning the cache key is pure savings. Invalidations are free for the first 1,000 paths per month, then about $0.005 per path, so prefer versioned filenames (logo.v2.png) over invalidating.
Best Practices
- Use the managed
CachingOptimizedpolicy for static assets andCachingDisabledfor APIs before building custom policies. - Keep the cache key minimal — include a header, cookie, or query string only when it actually changes the response.
- Route static and dynamic paths to different origins with ordered behaviors so each can have its own caching rules.
- Set sensible TTLs, but let the origin’s
Cache-Controlheader drive freshness when you can. - Forward data your origin needs via an origin request policy, not the cache policy, so logging needs do not shatter the cache.
- Use versioned asset filenames instead of frequent invalidations.
- Monitor the cache hit ratio in CloudFront reports; a low ratio almost always means an over-broad cache key.