Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Appendix B · HTTP Caching Headers Reference

A complete reference for every HTTP header relevant to CDN caching. Cross-referenced against RFC 7234 (HTTP/1.1 Caching), RFC 9110 (HTTP Semantics), RFC 9111 (HTTP Caching), and RFC 5861 (stale extensions).


Cache-Control

The primary mechanism for controlling caching behavior. Both request and response headers; semantics differ.

Response Cache-Control Directives

max-age=<seconds>

Content is fresh for this many seconds after the Date response header.

Cache-Control: max-age=3600

After 3600 seconds, the cached response is stale. CDN must revalidate or refetch from origin.

Gotcha: max-age is relative to Date, not to when the CDN received the response. If origin is slow and the response travels for 5 seconds, the CDN’s effective max-age is reduced by 5 seconds.

s-maxage=<seconds>

Overrides max-age for shared caches only (CDN, proxy). Does not affect browser cache. Ideal for: short browser TTL + long CDN TTL.

Cache-Control: max-age=60, s-maxage=3600

Browser caches the page for 1 minute; CDN caches it for 1 hour.

no-cache

Does not mean “don’t cache”. Means: store the response, but revalidate with origin before every use. Equivalent to max-age=0, must-revalidate.

Cache-Control: no-cache

The CDN will send a conditional request (If-None-Match or If-Modified-Since) for every cache hit. Origin returns 304 if unchanged (fast), or 200 + new body.

no-store

Do not cache at all. Not in memory, not on disk, not in shared caches.

Cache-Control: no-store

Use for: authentication tokens, session data, personalized responses.

private

Cache only in the browser (private cache). CDN/proxies must not cache.

Cache-Control: private, max-age=3600

Use for: user-specific pages (shopping cart, account dashboard).

public

Cache in all caches (browser + CDN), even for responses to requests with Authorization headers.

Cache-Control: public, max-age=86400

Without public, responses to authenticated requests are not cached by CDNs by default (even if max-age is set).

must-revalidate

Once stale, must revalidate before serving. Do not serve stale content even if the origin is unavailable.

Cache-Control: max-age=3600, must-revalidate

Contrast with stale-if-error which allows serving stale when origin fails.

proxy-revalidate

Same as must-revalidate but applies only to shared caches (CDN/proxy), not browsers.

immutable

The response body will not change during its freshness lifetime. Browser (and some CDNs) will not send conditional revalidation requests until after max-age expires.

Cache-Control: public, max-age=31536000, immutable

Use for: hashed assets (main.abc123.js), HLS segments, versioned files. Saves one conditional request per asset per page load.

stale-while-revalidate=<seconds>

After max-age expires, continue serving the stale response while revalidating in the background. See Lab 7.

Cache-Control: max-age=60, stale-while-revalidate=600

From 60s to 660s after Date: serve stale, trigger background revalidation. After 660s: must revalidate before serving.

stale-if-error=<seconds>

If origin returns 5xx (or is unreachable), serve the stale cached response for up to this many seconds beyond the normal expiry.

Cache-Control: max-age=3600, stale-if-error=86400

Serve stale for up to 24 hours during origin outage. Combine with stale-while-revalidate for both performance and resilience.

no-transform

Prohibit intermediate caches (CDN) from transforming the response body. Prevents CDN from applying compression, image optimization, or minification.


Request Cache-Control Directives

no-cache

Force the CDN to revalidate with origin before returning a cached response. Used by browsers when the user clicks “Refresh”.

no-store

Request that the response not be cached (hint; servers may ignore).

max-age=<seconds> (request)

Accept only responses not older than this many seconds (fresh or revalidated).

max-stale=<seconds> (request)

Accept stale responses up to this many seconds past their expiry.

min-fresh=<seconds> (request)

Accept only responses that will remain fresh for at least this many more seconds.

only-if-cached (request)

Return a cached response or 504 Gateway Timeout. Used by clients that want to avoid network requests.


Expires (Legacy)

The original HTTP/1.0 cache expiry mechanism. Specifies an absolute date:

Expires: Wed, 21 Oct 2025 07:28:00 GMT

Cache-Control: max-age overrides Expires when both are present. Expires is still useful for backwards compatibility with very old clients and proxies that don’t understand Cache-Control.

Gotcha: If Expires is in the past, the response is immediately stale. If the value is malformed or invalid, it’s treated as expired.


ETag

An opaque validator for a specific version of a resource:

ETag: "686897696a7c876b7e"
ETag: W/"686897696a7c876b7e"   (weak ETag)

Strong ETag ("...") means byte-for-byte identical content. Weak ETag (W/"...") means semantically equivalent content (may have different whitespace, compression, etc.).

Revalidation with ETag:

Client: GET /resource HTTP/1.1
        If-None-Match: "686897696a7c876b7e"

Origin: 304 Not Modified  (if ETag matches — no body)
        200 OK + new body + new ETag (if changed)

ETag best practices:

  • Use content hash (SHA-1, MD5, xxhash) for strong ETags
  • For database-backed content: use version_number or updated_at
  • Avoid using timestamps alone — they don’t reflect content changes and can cause spurious mismatches

Last-Modified / If-Modified-Since

The older revalidation mechanism (HTTP/1.0):

Response: Last-Modified: Tue, 15 Nov 2024 08:12:31 GMT

Request:  If-Modified-Since: Tue, 15 Nov 2024 08:12:31 GMT
Response: 304 Not Modified  (if not modified since that date)
          200 OK + new body  (if modified)

Use ETag when possible — it’s more precise. Last-Modified is only second-resolution; files updated multiple times per second may have the same Last-Modified but different content.


Vary

Tells caches that the response varies based on request headers:

Vary: Accept-Encoding
Vary: Accept-Language
Vary: Accept, Accept-Encoding

With Vary: Accept-Encoding, the CDN stores a separate cached response for each Accept-Encoding value:

GET /page.html  Accept-Encoding: gzip      → cache key: /page.html#gzip
GET /page.html  Accept-Encoding: br        → cache key: /page.html#brotli
GET /page.html  (no Accept-Encoding)       → cache key: /page.html#identity

Warning: Vary: User-Agent or Vary: Cookie creates cardinality explosion — a separate cache entry per unique User-Agent string (thousands). Avoid unless necessary. CDNs often ignore Vary: Cookie for this reason.


Surrogate-Control / Surrogate-Key (CDN-specific)

Not in RFCs; vendor-specific cache control for CDN-only directives:

Surrogate-Control: max-age=86400

This header is intended for the CDN only. The CDN strips it before forwarding to the browser, so the browser uses its own Cache-Control.

Surrogate-Key (Fastly) / Cache-Tag (Cloudflare):

Surrogate-Key: article-123 author-456 category-sports
Cache-Tag: article-123 author-456 category-sports

Associates the cached response with logical tags. Enables instant purge by tag rather than by URL. See Lab 9.


CDN-Cache-Control

A CDN-specific Cache-Control variant proposed as a standard (draft):

CDN-Cache-Control: max-age=600
Cache-Control: max-age=60

CDN respects CDN-Cache-Control (600s TTL) while browsers respect Cache-Control (60s TTL). Supported by Cloudflare, Fastly, and others. More explicit than s-maxage because it targets CDNs specifically rather than all shared caches.


Pragma: no-cache (Legacy)

HTTP/1.0 equivalent of Cache-Control: no-cache. Ignore in new code; handle for backwards compatibility:

Pragma: no-cache  →  treated as Cache-Control: no-cache by modern caches

Age

Set by the CDN/proxy to indicate how old a cached response is:

Age: 1234

Age: 1234 means this response was fetched from origin 1234 seconds ago. Remaining freshness = max-age - Age. If Age >= max-age, the response is stale even before it leaves the CDN.


Warning (Deprecated in RFC 9111)

Formerly used to indicate stale or revalidation state:

Warning: 110 - "Response is Stale"
Warning: 214 - "Transformation Applied"

RFC 9111 deprecated all Warning headers. Do not generate them; ignore if received.


Quick Reference Table

HeaderDirectionPurpose
Cache-Control: max-ageResponseCache TTL in seconds
Cache-Control: s-maxageResponseCDN-only TTL override
Cache-Control: no-cacheResponseRevalidate before use
Cache-Control: no-storeResponseNever cache
Cache-Control: privateResponseBrowser cache only
Cache-Control: publicResponseAll caches including CDN
Cache-Control: immutableResponseNever revalidate during freshness
Cache-Control: stale-while-revalidateResponseAsync background refresh
Cache-Control: stale-if-errorResponseServe stale on origin failure
ETagResponseVersion identifier
Last-ModifiedResponseLast change timestamp
VaryResponseDifferentiate cache by request headers
AgeResponseTime in cache (seconds)
ExpiresResponseAbsolute expiry date (legacy)
Surrogate-ControlResponseCDN-only TTL (stripped before browser)
Surrogate-Key / Cache-TagResponseLogical purge grouping
If-None-MatchRequestConditional request by ETag
If-Modified-SinceRequestConditional request by date
Cache-Control: no-cacheRequestForce CDN revalidation