Part of my AWS learning journey — exploring AWS hands-on and building a deeper understanding of Cloud & DevOps. This session dives into how Amazon CloudFront works as a CDN, from edge locations and cache hits/misses to cache keys, cache policies, TTLs, and invalidation.
📋 Topics Covered
| # | Topic | Type |
|---|---|---|
| 1 | What CloudFront Actually Is | Concept |
| 2 | Why Do We Need a CDN? | Concept |
| 3 | CloudFront Terminology — Distribution, Origin, Edge Location | Concept |
| 4 | Complete Request Flow | Concept |
| 5 | Cache HIT / Cache MISS | Concept |
| 6 | Per-Edge Caching | Concept |
| 7 | Cache Key | Concept + Interview |
| 8 | Cache-Key Design & The Golden Rule | Concept + Interview |
| 9 | Personalization and Caching | Concept + DevOps |
| 10 | Cache Policy vs Origin Request Policy | Concept + Interview |
| 11 | Query Strings, Cookies & Headers in the Cache Key | Concept |
| 12 | TTL — Minimum, Default, Maximum | Concept + Cert |
| 13 | Cache-Control & Invalidation | Concept |
| 14 | Versioned Assets | Concept + DevOps |
| 15 | Cache Behaviors | Concept |
| 16 | Real-World Caching Scenarios | Concept + DevOps |
| 17 | Request Collapsing & Cache Hit Ratio | Concept + Cert |
| 18 | Production Cache Hierarchy | Concept |
| 19 | CloudFront + S3 (OAC) | Concept + Lab |
| 20 | CloudFront + ALB + EC2 | Concept + Lab |
| 21 | CloudFront + WAF | Concept + DevOps |
| 22 | Production Techniques & Common Mistakes | DevOps |
| 23 | Production Mini Architecture | DevOps |
| 24 | Practice Labs 1–10 | Practice |
| 25 | Interview Revision | Interview |
| 26 | Final Mental Model | Concept |
What CloudFront Actually Is
Amazon CloudFront is AWS's Content Delivery Network (CDN).
The simplest mental model is:
CloudFront keeps reusable copies of content closer to users so that the origin does not have to generate or deliver the same response repeatedly.
Without CloudFront:
User
|
v
Origin
(EC2 / ALB / S3 / API Gateway)
|
v
Response
With CloudFront:
User
|
v
CloudFront Edge Location
|
+---- Cache HIT ----> Response to User
|
+---- Cache MISS ---> Origin
|
v
Response
|
v
CloudFront caches it
|
v
User
The origin remains the source of truth. CloudFront is a delivery and caching layer in front of it.
CloudFront can use origins such as Amazon S3, an Application Load Balancer, EC2/custom HTTP servers, API Gateway, and other HTTP origins.
Why Do We Need a CDN?
Imagine an application hosted in Mumbai. A nearby user may reach it with relatively low network latency, but a user in Europe has a much longer network path:
Europe User
|
| long network distance
v
Mumbai Origin
|
v
Response
With CloudFront:
Europe User
|
v
Nearby CloudFront edge
|
v
Cached response
If the response is already cached at that edge, the origin does not need to be contacted for that request.
CloudFront can therefore reduce viewer latency, reduce origin load, improve scalability, reduce repeated origin data transfer, provide globally distributed delivery, and integrate with security services such as AWS WAF and Origin Access Control.
Important CloudFront Terminology
Distribution
A CloudFront distribution is the overall configuration that tells CloudFront how to deliver an application. It contains configuration for origins, cache behaviors, cache policies, origin request policies, allowed HTTP methods, viewer protocol settings, TLS/custom domains, security, and logging.
Distribution = the overall CloudFront delivery configuration.
Origin
The origin is where CloudFront obtains the original content — for example CloudFront → S3, CloudFront → ALB → EC2, or CloudFront → API Gateway.
CloudFront
|
v
ALB
|
+--> EC2
+--> EC2
+--> EC2
Here, the ALB is the CloudFront origin.
Edge Location / Point of Presence (PoP)
An edge location is a CloudFront location close to viewers where CloudFront can serve cached content.
CloudFront
|
+-------------+-------------+
| | |
India Europe USA
| | |
Users Users Users
Different edge locations can have their own cached copies.
The origin stores the original content; edge locations store temporary cached copies.
CloudFront also has regional edge caches between POPs and origins. These provide another caching layer and can retain less-popular objects longer than individual POP caches.
Complete Request Flow
Suppose the origin contains logo.png and a user requests GET /logo.png. The conceptual flow is:
1. User requests /logo.png
|
v
2. DNS routes the viewer to an appropriate CloudFront edge
|
v
3. CloudFront determines the cache key
|
v
4. Cache lookup
/ \
HIT MISS
| |
v v
Return object Request origin
|
v
Origin response
|
v
CloudFront caches
|
v
User
This is the foundation of CloudFront.
Cache HIT / Cache MISS
A cache hit means CloudFront found a valid cached object matching the viewer's request, so the origin does not need to generate the object for that request. Benefits: lower latency, lower origin load, better scalability, better cache hit ratio.
A cache miss means CloudFront cannot find a valid cached object matching the request at that edge, so it must fetch the response from the origin and cache it. A later matching request can then become a cache hit while the object remains valid.
User User
| |
v v
CloudFront CloudFront
| |
v | MISS
Cache HIT v
| Origin
v |
Cached object response, then cached, then returned to User
Per-Edge Caching
Suppose a user in Delhi requests /logo.png. One edge location may cache it, while a user elsewhere may reach another edge location where that object is not yet cached:
Origin
|
+---------+---------+
| | |
Edge A Edge B Edge C
logo logo logo
The same object can exist in multiple edge caches. A cache miss at one edge does not mean the entire CloudFront distribution has no copy anywhere.
Cache Key
The cache key determines which viewer requests are considered equivalent for caching.
CloudFront's identity/checklist for deciding whether this request can reuse an existing cached object.
For GET /products?id=10, the cache identity could contain the path (/products) and query string (id=10). If another user sends the same relevant request, it can map to the same cached object.
Why Cache-Key Design Matters
If users A, B, and C all request /products?id=10 and receive the same product information, we want a single shared cache object serving all three — good cache reuse.
But if the cache key unnecessarily includes a unique cookie (e.g. user=A vs user=B), CloudFront now treats /products?id=10 + user=A and /products?id=10 + user=B as separate cache variants. The content may be identical, but cache reuse drops and origin traffic can increase.
The Golden Cache-Key Rule
Can this request value change the response?
If yes, it may need to participate in the cache key. If no, including it may unnecessarily fragment the cache.
For example, an analytics_id cookie that's only used for analytics and doesn't change the response usually shouldn't become a cache-key dimension. But a session_id cookie that determines whether the response is "Hello Alice" or "Hello Bob" is a personalized response and must not be accidentally shared.
Personalization and Caching
This is a major production concern. If GET /profile with Cookie: session=A returns "Hello Alice" and the same path with Cookie: session=B returns "Hello Bob", but CloudFront creates one shared cache object using only the path /profile, the response could be incorrectly reused. That is not merely a performance problem — it can become a data-isolation/security problem.
Common strategies:
Strategy 1 — Don't shared-cache private responses. For highly dynamic/private endpoints, route straight through: User → CloudFront → Origin.
Strategy 2 — Vary the cache identity when appropriate. For example, /profile + user=A → Cache A and /profile + user=B → Cache B. This can become extremely expensive and fragmented when there are millions of users.
Strategy 3 — Separate static and dynamic content. A common production architecture: /static/* is highly cacheable, /api/* is dynamic/private.
Cache Policy vs Origin Request Policy
A Cache Policy controls which request values participate in the cache key, TTL settings, and compression-related cache behavior. The cache key can be influenced by path, query strings, headers, and cookies.
Cache Policy answers: "What makes this request a different cache object, and how long should the object remain cached?"
An Origin Request Policy answers a different question:
What information should CloudFront send to the origin?
They are related, but not the same — this is one of the most important CloudFront interview concepts.
Example — origin needs information but cache doesn't
For a request like GET /products?id=10 with Cookie: analytics_id=ABC123, where id=10 determines the product but analytics_id is only needed by the origin for analytics and doesn't change the response, we want the cache key to stay just /products?id=10 while the origin request still forwards analytics_id=ABC123.
So: the Cache Policy does not include analytics_id in the cache key, while the Origin Request Policy forwards it to the origin. This allows cache reuse while still giving the origin the information it needs.
AWS documents this separation explicitly: values included in the cache key are also sent to the origin, while an Origin Request Policy can add additional headers, cookies, and query strings to origin requests without putting them into the cache key.
Warning with Authorization headers
If a request's Authorization header changes what response the user receives, you cannot simply forward the token and assume shared caching is safe. Always ask: does this request information change the response? If yes, either make the relevant dimension part of the cache design or avoid shared caching for that response.
Query Strings, Cookies & Headers in the Cache Key
Query strings: if id in /products?id=10 vs /products?id=20 changes the response, it must be represented in the cache identity — otherwise different products could map to the wrong cached response.
Cookies: if GET /products?id=10 returns the same response regardless of a user=A vs user=B cookie, don't automatically include the cookie in the cache key. If the response does differ because of the cookie, you need a design that prevents incorrect sharing. The answer is not always "include every cookie" — that can create millions of cache variants.
Headers: an Accept-Language: en request returning "Welcome" vs Accept-Language: fr returning "Bienvenue" means language may need to influence the cache identity if it genuinely changes the response.
Vary the cache only on dimensions that genuinely affect the response.
TTL — Minimum, Default, Maximum
TTL means:
How long a cached object remains fresh according to the CloudFront caching configuration.
With a TTL of 3600 seconds (one hour), an object cached at 12:00 stays a hit through 12:59, expires at 13:00, and needs fresh/validated origin content on the next request after that.
Long TTL (e.g. for app.js) gives high cache reuse, low origin traffic, low latency, and better scalability — at the cost of changes remaining stale longer unless you use versioned assets or invalidation.
Short TTL (e.g. for price.json) makes changes visible sooner, but increases origin requests, lowers the cache hit ratio, raises origin load, and can increase latency.
CloudFront cache policies have a Minimum TTL, Default TTL, and Maximum TTL, which work together with origin caching headers such as Cache-Control: max-age=3600.
- Default TTL is used when the origin doesn't provide appropriate caching information.
-
Minimum TTL sets a lower bound on how long CloudFront caches. Important production warning: if Minimum TTL is greater than zero, CloudFront can cache for at least that duration even if origin headers contain directives such as
no-cache,no-store, orprivate— so be careful using positive Minimum TTLs with private/dynamic content. - Maximum TTL places an upper bound on how long an object can remain fresh based on the relevant origin caching headers.
Cache-Control & Invalidation
The origin can send headers like Cache-Control: max-age=3600, Cache-Control: no-store, or Cache-Control: private to communicate caching requirements. CloudFront's cache policy and TTL configuration determine how those instructions interact with CloudFront caching.
If the origin's logo.png changes but CloudFront still has the old version cached with a 24-hour TTL, the old object can persist until it naturally expires. To remove it sooner, use CloudFront invalidation:
Origin = NEW
CloudFront Cache = OLD
|
v
Invalidation
|
v
Cached object invalidated
|
v
Next request -> MISS
|
v
Origin -> NEW
|
v
CloudFront caches NEW
Use TTL when "this content can safely remain cached for this long." Use invalidation when "I changed the content and need the cached version invalidated before its normal lifetime." Production systems commonly use both.
Versioned Assets
Instead of constantly replacing /app.js in place, use a content-hashed filename like /app.a82f91.js. When the application changes, /app.b73c21.js is generated — CloudFront sees a different cache key entirely, which makes long TTLs practical for build artifacts.
A common pattern: HTML gets a shorter TTL or controlled invalidation, while JS/CSS/images get a long TTL plus content-hashed filenames. This reduces the need for broad invalidations and is one of the most common production CDN techniques — using app.v1.js/app.v2.js or hash-based names avoids users being stuck on stale cached versions after a deployment.
Cache Behaviors
A Cache Behavior is a set of CloudFront rules applied to requests matching a URL path pattern (e.g. /static/*, /images/*, /api/*). Different behaviors can use different origins, cache policies, origin request policies, allowed methods, TTL/caching behavior, and viewer/security settings:
CloudFront
|
+-----------+-----------+
| |
/static/* /api/*
| |
v v
S3 ALB
|
EC2/ECS
A distribution has a default behavior (e.g. /*), and you can add more specific behaviors (e.g. /api/*, /images/*). The more specific matching behavior is used according to CloudFront's path matching rules — /images/logo.png uses the /images/* behavior, /api/products uses the /api/* behavior.
Real-World Caching Scenarios
Public product catalogue: GET /products?id=10 returning identical data to every user (User A → MISS → Origin → Cache, User B/C/D → HIT) is excellent cache reuse.
Personalized profile: GET /profile with different cookies returning "Hello Alice" vs "Hello Bob" must not become a shared cache object — a common fix is /static/* cached by CloudFront, /profile routed to a dynamic/private origin.
Analytics cookie: if GET /products?id=10 returns the same response regardless of an analytics_id cookie, that cookie shouldn't unnecessarily create separate cache objects — if the origin needs it, forward it via the origin request configuration without adding it to the cache key.
Language-specific content: Accept-Language: en vs fr returning "Welcome" vs "Bienvenue" means the caching design must distinguish the language variants.
Rapidly changing stock price: GET /stock/AAPL changing every second makes a long TTL inappropriate — options include a very short TTL, no caching, or a streaming/WebSocket mechanism for genuinely real-time requirements. CloudFront is not a "cache everything" service; caching is a business and correctness decision.
News website: /articles/* can use a longer TTL, /home a shorter TTL, and /api/personalized/* stays dynamic/private — different content has different freshness requirements.
Deployment: if app.js keeps the same filename across versions and the cached object is still valid, some users may keep receiving the old version. Using app.v1.js/app.v2.js or content hashes (app.abc123.js) avoids this — one of the most common production CDN techniques.
Request Collapsing & Cache Hit Ratio
Request collapsing: CloudFront can reduce duplicate origin requests when simultaneous requests for the same object and same cache key arrive at an edge while the object isn't available — 1,000 simultaneous requests can become a single origin fetch, with the response shared across all waiting requests. This only helps when requests share the same cache key; unnecessarily fragmenting the cache key (e.g. by user) makes the requests no longer equivalent for this purpose.
Cache hit ratio = cache hits ÷ total cacheable requests. For example, 800 hits out of 1,000 requests gives an 80% hit ratio. A higher hit ratio generally means more requests are served from edge cache and fewer reach the origin.
Do not optimize hit ratio at the expense of correctness or security.
A 99.9% hit ratio with users receiving incorrect/private data is a terrible architecture.
Production Cache Hierarchy
Viewer
|
v
CloudFront POP / Edge
|
v
Regional Edge Cache
|
v
Origin
The exact internal routing is managed by AWS, but conceptually CloudFront provides geographically distributed caching layers between viewers and the origin. Your application does not need to manually manage these edge caches.
CloudFront + S3 (OAC)
A classic architecture serves index.html, app.js, styles.css, and images/ out of S3 through CloudFront. For production, keep the S3 bucket private and let CloudFront access it using Origin Access Control (OAC):
Internet
|
v
CloudFront
|
| OAC
v
Private S3
This avoids making the S3 bucket itself the public application entry point.
CloudFront + ALB + EC2
Internet
|
v
CloudFront
|
v
ALB
|
+--> EC2-1
+--> EC2-2
+--> EC2-3
Responsibilities are kept separate: CloudFront handles CDN/edge caching, global delivery, viewer-facing TLS, WAF integration, and routing by cache behavior. ALB handles load balancing, health checks, and distribution across instances. EC2 handles application execution and business logic. Keeping these responsibilities separate is important when designing AWS architectures.
CloudFront + WAF
AWS WAF can inspect requests before they reach the application:
User
|
v
CloudFront
|
v
WAF rules
|
+---- malicious -> BLOCK
|
+---- valid ----> cache/origin
Possible protections include SQL injection rules, XSS-related rules, rate limiting, IP restrictions, AWS managed rule groups, and custom rules. WAF does not replace application authentication and authorization.
Production Techniques & Common Mistakes
Production techniques:
- Version static assets (
app.abc123.js,styles.72fd12.css) and use long TTLs where appropriate. - Separate static and dynamic paths (
/static/*,/images/*,/api/*) with a suitable behavior for each. - Minimize cache-key dimensions — avoid unnecessary cookies, headers, query strings, and tracking IDs.
- Use Origin Request Policy appropriately — forward what the origin needs without automatically turning every value into a cache-key dimension.
- Choose TTL by freshness requirements: stable static → long, frequently changing → short, private → usually not shared cached.
- Use OAC for private S3 origins (
CloudFront → OAC → private S3). - Use WAF to protect public CloudFront endpoints.
- Monitor cache hit ratio, request count, cache misses, origin request volume, origin latency, 4xx/5xx errors, and WAF blocked requests.
Common mistakes:
- Including every cookie in the cache key, creating huge cache fragmentation.
- Ignoring personalization, causing incorrect or unsafe response reuse.
- Using huge TTLs everywhere, creating stale-content problems.
- Using tiny TTLs everywhere, destroying much of the CDN benefit and increasing origin load.
- Confusing Cache Policy (what affects cache identity) with Origin Request Policy (what additional information goes to the origin).
- Relying only on invalidation — versioned assets are often cleaner for deployments.
- Making S3 public unnecessarily instead of using OAC for a private CloudFront-backed origin.
Production Mini Architecture
Internet
|
v
CloudFront
|
+-------------+-------------+
| |
/static/* /api/*
| |
v v
S3 ALB
|
EC2/ECS
|
Database
Security: CloudFront routes through AWS WAF, uses OAC for S3, and enforces HTTPS/TLS.
Caching: /static/* gets a long TTL with versioned assets; /api/public/* gets carefully configured caching; /api/cart/* and /api/profile/* get no/shared cache.
Practice Labs
Practice Lab 1 — Basic CloudFront + S3
Set up a CloudFront distribution in front of an S3 bucket, update the underlying S3 object, and observe how caching and invalidation affect whether the updated content is served immediately.
Practice Lab 2 — Observe HIT/MISS
Request a static object through CloudFront twice and identify the cache HIT/MISS behavior between the first and subsequent requests.
Practice Lab 3 — Query-String Cache Key
Configure a cache policy to include a specific query string, then verify that requests with different query-string values produce independent cache entries while repeated identical requests result in a cache hit.
Practice Lab 4 — Personalized Response Safety
Build an endpoint that returns different content based on a cookie, evaluate the risk of the cache key not distinguishing between users, and redesign the caching approach safely.
Practice Lab 5 — Cache Policy vs Origin Request Policy
Configure a request so an analytics cookie is forwarded to the origin without being included in the cache key, and confirm that cache reuse is preserved while the origin still receives the value it needs.
Practice Lab 6 — TTL Experiment
Set a short TTL on an object, change the origin content, and compare the object's behavior before and after TTL expiry against manually invalidating it.
Practice Lab 7 — Cache Behaviors
Configure separate cache behaviors for /static/* and /api/* with different origins and caching rules, connecting CloudFront to existing EC2/ALB infrastructure.
Practice Lab 8 — Private S3 + OAC
Configure CloudFront to access a private S3 bucket using Origin Access Control, and confirm that direct public access to the bucket remains blocked.
Practice Lab 9 — CloudFront + ALB + EC2
Route CloudFront traffic through an ALB to multiple EC2 instances, separate /static/* and /api/* paths, and determine which paths should be cached versus served dynamically.
Practice Lab 10 — Production-Style Mini Project
Build a production-style CloudFront architecture combining an S3 origin for static assets and an ALB/EC2 origin for API traffic, incorporating OAC, cache/origin request policies, TTL, invalidation, WAF, and monitoring — then validate the design against a range of caching, security, and failure scenarios.
Interview Revision
What is CloudFront?
CloudFront is AWS's CDN. It delivers content from geographically distributed edge locations and caches reusable origin responses closer to users, reducing latency and origin load.
What is a cache hit?
A cache hit occurs when the viewer request maps to a valid cached object, so CloudFront can return it without fetching that object from the origin.
What is a cache miss?
A cache miss occurs when CloudFront cannot find a valid matching cached object, so it obtains the object from the origin and can cache the response.
What is a cache key?
The cache key identifies a cached object. It can include the path and, when configured, selected query strings, headers, and cookies.
Cache Policy vs Origin Request Policy?
Cache Policy controls what contributes to the cache key and TTL behavior. Origin Request Policy controls additional headers, cookies, and query strings that CloudFront sends to the origin without necessarily making them part of the cache key.
Why can including too many cookies be bad?
It creates many cache variants, reduces cache reuse and can lower the cache hit ratio.
Why can excluding a response-changing cookie be dangerous?
Different users can map to the same cache object and potentially receive the wrong personalized response.
Final Mental Model
VIEWER REQUEST
|
v
CloudFront
|
Build cache key
|
v
Cache lookup
/ \
HIT MISS
| |
v v
Cached Origin
object |
| v
| Response
| |
| v
| Cache it
| |
+------<--------+
|
v
Viewer
And this distinction:
Cache Policy → what makes requests different? how long can objects be cached?
Origin Request Policy → what additional information does the origin need?
The single most useful rule:
Cache only what can safely be reused, make the cache key vary only on information that changes the response, and choose TTL according to the required freshness.
Another step in the AWS learning journey — understanding not just what CloudFront does, but why caching, routing, and security decisions matter in real-world systems.
Top comments (0)