October 12, 2026 · 3 min read · Yunus Emre Vurgun
HTTP Caching with ETags and Cache-Control
A cache needs two separate decisions: whether it may reuse a response now, and how to check the response after it becomes stale. Cache-Control answers the first question. Validators such as ETag help answer the second. Mixing those jobs produces either needless downloads or unexpectedly old data.
Consider a public dictionary downloaded by many clients. A five-minute freshness window is acceptable, but sending the entire file every five minutes is wasteful. Give the representation a validator that changes when its content changes, and make the cache policy explicit. Do not use a personalized response for this example: shared caching requires a separate privacy decision.
A complete request sequence
GET /dictionary.json HTTP/1.1
HTTP/1.1 200 OK
Cache-Control: public, max-age=300
ETag: "dictionary-7"
Content-Type: application/json
During freshness, a cache may reuse the response without asking the origin. After that interval, the client sends If-None-Match: "dictionary-7". If the selected representation still matches, the server responds with 304 and cache metadata. The client keeps its stored body. If the representation changed, the server returns the new body with a new validator.
Handle 304 before trying to parse a response as JSON. There is no replacement representation body to parse. A small command-line client that persists its own cache must also retain the previous body and apply response metadata correctly; a conditional request alone does not create a cache.
Choose a policy by the content
For a hashed stylesheet, a long freshness lifetime with immutable is appropriate when a content change also changes the URL. For an editable public reference file, choose a bounded lifetime or require validation. For a user-specific response, consider private or no-store according to the actual storage requirements. No-cache permits storage but requires validation before reuse; it is not a synonym for no-store.
Test both the origin and any CDN. A CDN can add age, vary cache keys, or impose its own configuration. Record the response headers from the first request, a fresh reuse, and a stale validation. That three-step trace is more useful than checking only that one header exists.
Verify the failure paths
- Change the body and confirm the validator changes.
- Check negotiated variants so one representation does not reuse another’s validator incorrectly.
- Confirm authenticated content never enters an unintended shared cache.
- Test a 304 with an existing cached body and a client with no cached body.
Reference tables to keep nearby
- HTTP Cache-Control Directives — Cache-Control directives for browser and shared caches, with example values, intended behavior, and common configuration mistakes.
- HTTP Conditional Requests and Validators — Conditional request headers, comparison rules, and response outcomes for safe caching and lost-update protection.
Primary sources
These are the specifications and project documentation behind the examples. Check the relevant implementation and version before applying a pattern.