Shaduf.
HTTP Cache Field Guide/HTTP caching basics — dated baseline

← Start here

Bootstrap report: freshness, validation, and safe storage

Research run: 16 Sep 2026 · normative base: RFC 9111 and RFC 9110.

Executive answer

  1. Freshness permits reuse without contacting the origin; it is not a command to repaint a page.
  2. Stale responses normally revalidate. A conditional request can produce 304 (reuse stored bytes) or 200 (replace them).
  3. no-cache allows storage but requires validation before reuse; no-store tells compliant caches not to intentionally store.
  4. private blocks shared-cache reuse but does not encrypt the response or make it a complete confidentiality guarantee.
  5. s-maxage overrides max-age in shared caches; private browser caches ignore it.

The state timeline

1 · Miss
Origin 200; eligible response is stored.
2 · Fresh
current_age < lifetime; cache may answer without a request.
3 · Stale
Usually revalidate, unless a permitted stale path applies.
4 · Conditional
If-None-Match takes precedence over If-Modified-Since.
5 · Result
304 updates metadata and keeps stored bytes; 200 replaces them.

Stale is not always unusable: stale-while-revalidate and stale-if-error are bounded extension permissions, not a promise that every cache implements them.

Small policy table

SituationStarting policyCheck
Personalized responseprivate, no-cacheCookies/auth, cache key, and absence of shared-cache evidence.
Public dynamic responsepublic, max-age=60, s-maxage=10Repeat request, Age, optional Cache-Status, and validators.
Fingerprint/versioned assetpublic, max-age=31536000, immutableNew URL for changed bytes; confirm deployment routing.
Do not retainno-storeRemember this does not erase an older entry.

Copyable checks

curl -sS -D /tmp/headers -o /tmp/body "$URL"
grep -iE '^(HTTP/|cache-control:|etag:|last-modified:|age:|cache-status:|vary:|via:)' /tmp/headers
curl -sS -D - -o /dev/null -H 'Cache-Control: no-cache' "$URL"

A 304 proves conditional semantics at the responding hop, not that a browser cache or CDN caused it. A missing Age or Cache-Status proves little: those fields are not guaranteed through every layer. DevTools’ Disable cache changes the experiment and mainly exposes the browser hop.

Worked example: Authorization at two cache layers

Request: GET /account with Authorization: Bearer …. Imagine the response is Alice's account page.

LayerSafe reading
Private browser cacheCache-Control: private, no-cache may let Alice's browser retain a copy, then validate it before reuse with ETag. This is still browser-local storage, not encryption.
Shared cache/CDNDo not share a user-specific response. private prevents shared-cache storage; no-store avoids intentional HTTP-cache retention when persistence is unacceptable.

For a request carrying Authorization, RFC 9111 §3.5 requires an explicit response permission such as public, s-maxage, or must-revalidate before a shared cache may reuse it. That permission is appropriate only when the representation is genuinely safe to share and the cache key is deliberately correct—not merely because a request was authenticated.

Authorization: Bearer alice-token
Cache-Control: private, no-cache
ETag: "acct-alice-v7"

Diagnostic contrast: a browser hit or browser-side 304 says little about a CDN. Inspect Age, Cache-Status, cache-key inputs, and the response policy at the shared hop.

Scope and uncertainty

HTTP standards define semantics but do not force every cache to store or reuse a response. Browser history/bfcache, service workers, reverse proxies, CDNs, cache keys, POPs, and vendor configuration can change what a developer observes. This baseline does not settle Vary, cookies, cache-key interactions beyond this example, service-worker Cache API, or product invalidation recipes; later research will treat those separately.

Sources

Search published pools, pages, reports, and evidence.