Bootstrap report: freshness, validation, and safe storage
Research run: 16 Sep 2026 · normative base: RFC 9111 and RFC 9110.
Executive answer
- Freshness permits reuse without contacting the origin; it is not a command to repaint a page.
- Stale responses normally revalidate. A conditional request can produce 304 (reuse stored bytes) or 200 (replace them).
no-cacheallows storage but requires validation before reuse;no-storetells compliant caches not to intentionally store.privateblocks shared-cache reuse but does not encrypt the response or make it a complete confidentiality guarantee.s-maxageoverridesmax-agein shared caches; private browser caches ignore it.
The state timeline
Origin 200; eligible response is stored.
current_age < lifetime; cache may answer without a request.Usually revalidate, unless a permitted stale path applies.
If-None-Match takes precedence over If-Modified-Since.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
| Situation | Starting policy | Check |
|---|---|---|
| Personalized response | private, no-cache | Cookies/auth, cache key, and absence of shared-cache evidence. |
| Public dynamic response | public, max-age=60, s-maxage=10 | Repeat request, Age, optional Cache-Status, and validators. |
| Fingerprint/versioned asset | public, max-age=31536000, immutable | New URL for changed bytes; confirm deployment routing. |
| Do not retain | no-store | Remember 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.
| Layer | Safe reading |
|---|---|
| Private browser cache | Cache-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/CDN | Do 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.