CloudFront Cache Behaviors, TTL and Invalidation Step by Step
In CloudFront Origins Explained we sent /static/* to S3 and everything else to an ALB. The rule that did that is a cache behavior. This chapter goes deep: path patterns and their order, TTLs, Cache-Control headers, cache policies vs origin request policies, the cache key, and how to update files with invalidations or versioned names.
What you'll learn in this chapter
- Cache behaviors, path patterns and their order
- Minimum, default and maximum TTL with Cache-Control
- Cache policy vs origin request policy
- The cache key: query strings, cookies, headers
- Invalidation vs versioned file names
1. A story: the old price list
A shop prints its price list and gives copies to 20 branch counters. Next week prices change. The head office has the new list, but branches keep handing out the old copies until someone tells them to throw them away.
CloudFront edges are those branches. The TTL is how long each branch keeps a copy. Invalidation is the phone call that says "throw it away".
2. Cache behaviors and path patterns
Why do we need this?
Images can be cached for a year; the login API must never be cached. One distribution needs different rules for different paths.
What is it?
A cache behavior = a path pattern + an origin + settings (viewer protocol, allowed methods, cache policy, origin request policy, functions, signed URL requirement). CloudFront checks behaviors top to bottom and uses the first match. The Default (*) behavior is always last and catches everything else.
| Order | Path pattern | Origin | Cache |
|---|---|---|---|
| 0 | /api/* | ALB | CachingDisabled |
| 1 | /static/* | S3 | 1 year (versioned files) |
| 2 | *.jpg | S3 | 1 day |
| 3 | /images/private/* | S3 | Signed URL required |
| Default | * | ALB | Short, e.g. 60 s for HTML |
Trap: /images/private/photo.jpg matches *.jpg (order 2) before /images/private/* (order 3), so the private rule never runs. Put specific patterns above general ones. Patterns are case sensitive; * matches any characters and ? exactly one.
3. TTL and Cache-Control
Why do we need this?
Too short and the origin works hard. Too long and users see old files.
What is it?
Your origin can send Cache-Control: max-age=600 (or s-maxage for shared caches only). The cache policy has three TTLs that frame it:
| TTL | Meaning | CachingOptimized value |
|---|---|---|
| Minimum | Cache at least this long, even if the origin says less | 1 s |
| Default | Used when the origin sends no Cache-Control or Expires | 86,400 s (1 day) |
| Maximum | Never cache longer than this, even if the origin says more | 31,536,000 s (1 year) |
| Origin sends | Min 0, Default 86400, Max 3600 | Edge keeps it |
|---|---|---|
| max-age=600 | - | 600 s |
| nothing | - | 3600 s (default capped by max) |
| max-age=86400 | - | 3600 s (capped by max) |
| no-cache / no-store / private | Min 0 | Not cached |
| Stack | Send Cache-Control |
|---|---|
| S3 upload | aws s3 cp app.3f9a.js s3://b/static/ --cache-control "public,max-age=31536000,immutable" |
| Node/Express | express.static("public",{maxAge:"1y",immutable:true}) |
| Flask | resp.headers["Cache-Control"]="public, max-age=600" |
| PHP | header("Cache-Control: public, max-age=600"); |
| Spring | ResponseEntity.ok().cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES)) |
| .NET | [ResponseCache(Duration = 600)] |
| nginx | location /static/ { add_header Cache-Control "public, max-age=31536000, immutable"; } |
| Apache | Header set Cache-Control "max-age=600, public" |
4. Cache policy vs origin request policy
Why do we need this?
Your app may need a cookie to work, but if every cookie value becomes part of the cache key, every user gets a separate copy and the hit rate drops to zero.
What is it?
| Point | Cache policy | Origin request policy |
|---|---|---|
| Controls | TTLs + the cache key (what makes two requests "the same") | Extra values sent to the origin on a miss |
| Affects hit rate | Yes | No |
| Managed examples | CachingOptimized, CachingDisabled, UseOriginCacheControlHeaders | AllViewer, CORS-S3Origin, AllViewerExceptHostHeader |
| Rule | Values in the cache key are always sent to the origin too | Add what the origin needs but caching should ignore |
5. The cache key
Why do we need this?
The cache key decides whether two requests share one cached copy. A bigger key means more copies and more misses.
What is it?
By default the key is the domain plus the URL path. You can add query strings, cookies and headers. Add only what really changes the response.
| Request | Put in cache key? | Why |
|---|---|---|
| /products?page=2 | Query string page: yes | Different content per page |
| /products?utm_source=fb | utm_*: no | Same content; would split the cache |
| Cookie: lang=mr | Only cookie lang | Page differs by language |
| Cookie: _ga=... | No | Analytics only |
| Accept-Encoding | Handled by enabling Gzip/Brotli in the policy | Compressed and plain copies kept apart |
6. Invalidation vs versioned file names
Why do we need this?
You changed a file. Edges still hold the old copy until TTL ends. You need a way to update now.
What is it?
| Point | Invalidation | Versioned names (app.3f9a.js) |
|---|---|---|
| How | Tell CloudFront to drop paths from every edge | New content = new file name; HTML points to it |
| Cost | First 1,000 paths per month free, then $0.005 per path; /* counts as one path | Free |
| Speed | Usually done within a few minutes | Instant: new URL is a guaranteed miss |
| Rollback | Invalidate again | Point HTML back to the old name |
| Best for | index.html, emergency fixes | JS, CSS, images (Vite, webpack, Angular do it for you) |
# invalidate one file, then wait for it
aws cloudfront create-invalidation --distribution-id E1ABCDEF2GHIJ --paths "/index.html"
aws cloudfront wait invalidation-completed --distribution-id E1ABCDEF2GHIJ --id I2J3K4L5M6N7O8
Ravindra Bagale's Tip
Common pattern: index.html with a short TTL (60 s) or invalidated on each deploy; every asset versioned and cached for a year.
7. Lab: a stale file, then fix it
How to do it (step by step)
- Use a distribution with a private S3 origin and OAC (the lab from What Is Amazon CloudFront), default behavior with CachingOptimized.
echo "price v1" > price.txt; aws s3 cp price.txt s3://yourname-cf-lab/price.txt.curl -s https://dXXXX.cloudfront.net/price.txttwice. Expected: price v1; second time X-Cache Hit (check with -I).- Change it:
echo "price v2" > price.txt; aws s3 cp price.txt s3://yourname-cf-lab/price.txt. curl -s https://dXXXX.cloudfront.net/price.txt. Expected: still price v1. Why: the edge copy lives for the 1-day default TTL.- Invalidate:
aws cloudfront create-invalidation --distribution-id EXXXX --paths "/price.txt". Note the Id in the output. aws cloudfront wait invalidation-completed --distribution-id EXXXX --id IXXXX. Expected: returns with no output when done.curl -sI https://dXXXX.cloudfront.net/price.txt | grep -i x-cache; curl -s https://dXXXX.cloudfront.net/price.txt. Expected: Miss, price v2.- Now the versioned way: upload
price.v3.txtwith--cache-control "public,max-age=31536000,immutable"and fetch it. Expected: new content at once, no invalidation. - Cache key test:
curl -sI "https://dXXXX.cloudfront.net/price.txt?x=1" | grep -i x-cache. Expected: Hit. Why: CachingOptimized ignores query strings, so ?x=1 is the same key.
Cleanup
- Delete the test objects; disable and delete the distribution if you made one only for this lab.
8. Interview questions
Q. In what order are cache behaviors evaluated?
Top to bottom by precedence; the first matching path pattern wins; Default (*) is last.
Q. Origin sends max-age=86400 but max TTL is 3600. How long is it cached?
3600 seconds. The max TTL caps it.
Q. Cache policy vs origin request policy?
Cache policy sets TTLs and the cache key; origin request policy adds values sent to the origin without affecting the cache key.
Q. Why is your hit ratio low?
The cache key probably includes all cookies, headers or query strings. Include only what changes the response.
Q. Invalidation or versioned file names?
Versioned names for assets (free, instant, easy rollback); invalidation for index.html and emergencies (1,000 free paths/month).
Try at home
- Create a custom cache policy that keys on the query string "page" only and test two pages.
- Add a /api/* behavior with CachingDisabled above the default and confirm Miss every time.
In short
- Behaviors match paths top to bottom; first match wins.
- Min/default/max TTL frame the origin's Cache-Control.
- Cache policy = key + TTL; origin request policy = extra data to origin.
- Keep the cache key small for a high hit rate.
- Version assets; invalidate only index.html.
Practice Tasks
Zurich Insurance Partners is a insurance business in Zurich. Anna Müller looks after its IT and cloud work. They count money in CHF and run their AWS resources in the eu-central-2 Region. In the tasks below you do real jobs for them using what this lesson teaches. All names, account IDs, IP addresses and numbers are made up for practice.
0 / 10
Easy List distributions
Why: Find the distribution ID and domain name.
What to do: List all CloudFront distributions.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Easy How many addresses in a /16?
Why: Picking a VPC size needs this count.
What to do: Zurich Insurance Partners plans the VPC 10.189.0.0/16. How many IP addresses does it hold in total?
- Write down the numbers the task gives you.
- Work it out step by step on paper.
- Type only the final answer in the box.
- Click Check answer.
Easy Port for SSH
Why: Security group rules need the right port.
What to do: Which TCP port must the security group open for SSH?
- Write down the numbers the task gives you.
- Work it out step by step on paper.
- Type only the final answer in the box.
- Click Check answer.
Medium Clear the CloudFront cache
Why: After a deploy, invalidate so visitors get the new files now, not after the TTL.
What to do: Invalidate all paths (/*) on distribution E2QWRUHAPOMQZL.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Hard Create a distribution from a config file
Why: The full config (origins, behaviors, certificate) is easiest to keep in JSON.
What to do: Create a CloudFront distribution from dist.json.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Hard Invalidate two paths
Why: Invalidating only what changed is cheaper and faster.
What to do: On E2QWRUHAPOMQZL, invalidate /index.html and /css/site.css.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Hard Bucket policy for CloudFront OAC
Why: With OAC, only your CloudFront distribution may read the private bucket.
What to do: Allow the service cloudfront.amazonaws.com to s3:GetObject on zurichinsure-site/*, only when aws:SourceArn equals arn:aws:cloudfront::131130276334:distribution/E2QWRUHAPOMQZL.
- Start with { "Version": "2012-10-17", "Statement": [ ... ] }.
- In each statement write Effect, Action and Resource.
- Give only what the task needs (least privilege).
- Use double quotes and commas correctly. Click Check answer.
Hard Create an Origin Access Control
Why: OAC lets only CloudFront read your private S3 bucket.
What to do: Create an origin access control from oac.json.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Hard Request a free certificate for CloudFront
Why: CloudFront only uses ACM certificates from us-east-1. DNS validation renews automatically.
What to do: Request a certificate for zurichinsure.ch and www.zurichinsure.ch with DNS validation, in us-east-1.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Hard Make a CloudFront signed URL
Why: Signed URLs let only paying users watch a video, for a limited time.
What to do: Sign https://d111.cloudfront.net/course/intro.mp4 with key pair ID K2JCJMDEHXQW5F, private key private_key.pem, expiring 2026-12-31.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Revision, comparison and interview cards
Revise this lesson in a few minutes. Save the cards on your phone or download them as PNG files. The QR code on each card opens this lesson.






More cards: Ravi Sir's Cards.