πŸ”’ Cloudflare Cache Control Header Prioritization

Decision tree logic for cache behavior and TTL expiration

Priority 1 (Highest)
Priority 2
Priority 3
Priority 4
Priority 5
Priority 6 (Lowest)

πŸ§ͺ Complete Cache Behavior Evaluator

Configure all cache parameters to evaluate freshness, retention, revalidation, stale behavior, and request collapsing across Origin, Edge, and Browser layers.

TTL / Freshness Configuration

Stale Content & Revalidation

Cacheability Directives

Request Context & Conditions

πŸ“Š Header Priority Order (Highest to Lowest) - Edge Cache

1a. cacheTtl (Workers fetch option) HIGHEST - Developers EDGE
Set programmatically via fetch(url, { cf: { cacheTtl: 3600 } }). Per-request override when using Cloudflare Workers.
1b. Edge Cache TTL (Cache Rules) HIGHEST - Dashboard EDGE
Set via Cloudflare Dashboard β†’ Cache Rules. Overrides ALL origin headers. Best for most users without Workers.
2. Cloudflare-CDN-Cache-Control EDGE
Cloudflare-specific header. Not proxied downstream. Most specific CDN control for Cloudflare only.
3. CDN-Cache-Control EDGE
Standard CDN header. Proxied downstream to other CDNs. Controls CDN behavior separately from browsers.
4. Surrogate-Control EDGE
If present, Cloudflare ignores Cache-Control directives entirely for edge caching.
5. Cache-Control: s-maxage EDGE
Shared cache TTL. Overrides max-age for CDN/proxy caches. Browsers ignore this directive.
6. Cache-Control: max-age LOWEST EDGE BROWSER
Standard HTTP header. Used when no CDN-specific headers present. Applies to both edge and browser.

🌐 Complete Cache Layer Scenarios

🏠 Origin Server

Sets response headers
Cache-Control, CDN-Cache-Control, Surrogate-Control, Expires
Controls freshness
Determines how long content is considered valid
Revalidation headers
ETag, Last-Modified for conditional requests

⚑ Cloudflare Edge

Cache Rules override
Edge Cache TTL overrides all origin headers
Workers cacheTtl
Programmatic control per-request
CDN-specific headers
Cloudflare-CDN-Cache-Control, CDN-Cache-Control
s-maxage respected
Shared cache TTL from Cache-Control

🌐 Browser

Browser Cache TTL
Cache Rules can override max-age sent to browser
Cache-Control: max-age
Primary browser cache directive
Expires header
Legacy fallback if no max-age
Ignores s-maxage
Browser only reads max-age, not s-maxage

πŸ“‹ Edge Cache Use Cases

Scenario 1: Workers cacheTtl Override

fetch(url, { cf: { cacheTtl: 300 } })
Origin: Cache-Control: max-age=3600
Edge: 300s (cacheTtl wins) Browser: 3600s (max-age)

Scenario 2: Edge Cache TTL Rule

Cache Rule: Edge TTL = 86400
Origin: Cache-Control: max-age=3600, s-maxage=7200
Edge: 86400s (rule wins) Browser: 3600s (max-age)

Scenario 3: Cloudflare-CDN-Cache-Control

Cloudflare-CDN-Cache-Control: max-age=1800
CDN-Cache-Control: max-age=3600
Cache-Control: max-age=7200
Edge: 1800s (CF-CDN wins) Browser: 7200s (max-age)

Scenario 4: CDN-Cache-Control Only

CDN-Cache-Control: max-age=3600
Cache-Control: max-age=7200, s-maxage=1800
Edge: 3600s (CDN-CC wins over s-maxage) Browser: 7200s (max-age)

Scenario 5: Surrogate-Control Present

Surrogate-Control: max-age=600
Cache-Control: max-age=3600, s-maxage=7200
Edge: 600s (Surrogate wins, CC ignored) Browser: 3600s (max-age)

Scenario 6: s-maxage vs max-age

Cache-Control: public, max-age=300, s-maxage=3600
Edge: 3600s (s-maxage for shared cache) Browser: 300s (max-age only)

Scenario 7: Browser Cache TTL Override

Cache Rule: Browser TTL = 86400
Origin: Cache-Control: max-age=300
Edge: 300s (no edge override) Browser: 86400s (rule overrides)

Scenario 8: No Headers (Default)

No cache headers from origin
File: style.css (cacheable extension)
Edge: Default TTL by status code Browser: Heuristic caching

🚫 DO NOT CACHE Conditions

Cache-Control: no-store
Never store response anywhere
Cache-Control: private
Only browser can cache, not CDN
Cache-Control: max-age=0
With Origin Cache Control OFF β†’ BYPASS
Set-Cookie header present
With default cache level + OCC enabled
Non-GET request method
POST, PUT, DELETE, etc. are not cached
Authorization header
Unless must-revalidate, public, or s-maxage present

βœ… CACHE Conditions

Cache-Control: public, max-age>0
Standard cacheable response
Expires header (future date)
Legacy caching. max-age takes precedence if both set
Cacheable file extension
.js, .css, .png, .jpg, .gif, .ico, .woff, etc.
s-maxage directive
Overrides max-age for shared caches (CDN)
Cache Everything rule + Edge TTL
Forces caching even with Set-Cookie

⏱️ TTL Expiration Priority

Priority Source Affects Behavior
1 Edge Cache TTL (Cache Rules) Cloudflare Edge Overrides all origin headers for edge caching
2 Cloudflare-CDN-Cache-Control: max-age Cloudflare Edge only Not proxied downstream
3 CDN-Cache-Control: max-age All CDNs Proxied to downstream CDNs
4 Cache-Control: s-maxage Shared caches (CDN) Overrides max-age for CDN, browsers ignore
5 Cache-Control: max-age All caches Default TTL for browsers and CDN
6 Expires header All caches Legacy. Ignored if max-age present
7 Cloudflare Default TTL Cloudflare Edge Based on status code when no headers present

πŸ”„ Cache Decision Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                              INCOMING REQUEST                                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β”‚
                                        β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚     Is request method GET or HEAD?     β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚                    β”‚
                          NO                   YES
                           β”‚                    β”‚
                           β–Ό                    β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚   BYPASS    β”‚    β”‚   Edge Cache TTL rule exists?         β”‚
                    β”‚  (DYNAMIC)  β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚                    β”‚
                                            YES                   NO
                                             β”‚                    β”‚
                                             β–Ό                    β–Ό
                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                    β”‚  Use Edge   β”‚    β”‚ Cloudflare-CDN-Cache-Control exists?  β”‚
                                    β”‚  Cache TTL  β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚                    β”‚
                                                            YES                   NO
                                                             β”‚                    β”‚
                                                             β–Ό                    β–Ό
                                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                    β”‚ Use Cloudflare- β”‚  β”‚   CDN-Cache-Control exists?           β”‚
                                                    β”‚ CDN-Cache-Ctrl  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚                    β”‚
                                                                              YES                   NO
                                                                               β”‚                    β”‚
                                                                               β–Ό                    β–Ό
                                                                      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                                      β”‚ Use CDN-    β”‚    β”‚   Surrogate-Control exists?           β”‚
                                                                      β”‚ Cache-Ctrl  β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚                    β”‚
                                                                                              YES                   NO
                                                                                               β”‚                    β”‚
                                                                                               β–Ό                    β–Ό
                                                                                      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                                                      β”‚  Ignore     β”‚    β”‚   Check Cache-Control directive       β”‚
                                                                                      β”‚Cache-Controlβ”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β”‚
                                                                                                                         β–Ό
                                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                          β”‚                           CACHE-CONTROL EVALUATION                                                β”‚
                                          β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
                                          β”‚  no-store          β†’  BYPASS (never cache)                                                       β”‚
                                          β”‚  private           β†’  BYPASS (browser only)                                                      β”‚
                                          β”‚  no-cache + OCC ON β†’  CACHE + always revalidate                                                  β”‚
                                          │  no-cache + OCC OFF→  BYPASS                                                                     │
                                          │  max-age=0 + OCC ON→  CACHE + revalidate                                                         │
                                          │  max-age=0 + OCC OFF→ BYPASS                                                                     │
                                          β”‚  s-maxage > 0      β†’  CACHE (use s-maxage for edge TTL)                                          β”‚
                                          β”‚  max-age > 0       β†’  CACHE (use max-age for TTL)                                                β”‚
                                          β”‚  public            β†’  CACHE                                                                      β”‚
                                          β”‚  No directive      β†’  Use default TTL based on status code                                       β”‚
                                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                

πŸ” Revalidation & Stale Content

Revalidation Headers

If-Modified-Since + Last-Modified
If-None-Match + ETag
must-revalidate - Must check origin when stale
proxy-revalidate - CDN must revalidate (not browsers)

Stale Content Directives

stale-while-revalidate=<seconds>
Serve stale while fetching fresh in background
stale-if-error=<seconds>
Serve stale if origin returns 5xx error

πŸ“‹ TTL Examples by Layer

Example 1: Different TTLs per layer

Cache-Control: max-age=14400, s-maxage=84000 Cloudflare-CDN-Cache-Control: max-age=24400 CDN-Cache-Control: max-age=18000
Browser14,400s (4h)
Cloudflare Edge24,400s (6.8h)
Other CDNs18,000s (5h)
Shared Caches84,000s (23h)

Example 2: Stale handling on error

Cache-Control: stale-if-error=400 Cloudflare-CDN-Cache-Control: stale-if-error=60 CDN-Cache-Control: stale-if-error=200
Browser/Origin400s stale on error
Cloudflare Edge60s stale on error
Other CDNs200s stale on error

⚑ Origin Cache Control (OCC) Behavior Matrix

Directive OCC Disabled OCC Enabled
s-maxage=0 Will cache using default TTL Will not cache
max-age=0 Will not cache (BYPASS) Cache + revalidate on each request
no-cache MISS in logs BYPASS in logs, always revalidate
no-store Will not cache Will not cache
private Will cache (strips header) Will not cache
Authorization header Content may be cached Only if must-revalidate, public, or s-maxage present
Set-Cookie + default level Cache (strips Set-Cookie) BYPASS (preserves Set-Cookie)

πŸ“Š CF-Cache-Status Response Values

HIT
Served from cache
MISS
Not in cache, fetched from origin
EXPIRED
Was cached but stale, fetched fresh
STALE
Served stale (origin unreachable)
BYPASS
Origin instructed not to cache
REVALIDATED
Stale but confirmed valid via 304
UPDATING
Served stale while revalidating
DYNAMIC
Not eligible for caching

🎯 Quick Reference: Common Use Cases

Use Case Header Configuration
Cache static asset for 1 day Cache-Control: public, max-age=86400
Never cache sensitive data Cache-Control: no-store
Browser only (no CDN cache) Cache-Control: private, max-age=3600
Cache but always revalidate Cache-Control: public, no-cache
Different TTL for CDN vs Browser Cache-Control: public, max-age=7200, s-maxage=3600
Serve stale during revalidation Cache-Control: max-age=600, stale-while-revalidate=30
Serve stale on origin error Cache-Control: public, max-age=3600, stale-if-error=60
Cloudflare-specific TTL only Cloudflare-CDN-Cache-Control: max-age=86400

Source: Cloudflare Cache Documentation | Generated from developers.cloudflare.com/cache/concepts/