DEV Community

Tejas Shinkar
Tejas Shinkar

Posted on

AWS CloudFront — Complete Understanding, Production Patterns & Practice

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
Enter fullscreen mode Exit fullscreen mode

With CloudFront:

User
  |
  v
CloudFront Edge Location
  |
  +---- Cache HIT ----> Response to User
  |
  +---- Cache MISS ---> Origin
                           |
                           v
                        Response
                           |
                           v
                    CloudFront caches it
                           |
                           v
                        User
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

With CloudFront:

Europe User
     |
     v
Nearby CloudFront edge
     |
     v
Cached response
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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, or private — 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)