Menu

Topic 2 of 8

Caching Headers And ETags

Learn Caching Headers And ETags for free with explanations, exercises, and a quick test (for API Engineer).

Published: January 21, 2026 | Updated: January 21, 2026

Why this matters

As an API Engineer, you are responsible for fast, reliable, and cost-efficient APIs. Correct caching headers and ETags can cut latency, reduce server load, and improve user experience without changing business logic.

  • Lower p95 latency by serving responses from browser or CDN caches.
  • Slash origin traffic during spikes with shared-cache directives.
  • Prevent stale or private data leaks by scoping cache correctly.
  • Use conditional requests (ETag/If-None-Match) to avoid re-sending bodies when nothing changed.

Concept explained simply

Caching is a contract between your API and caches (browsers, CDNs, proxies). You tell caches how long to keep a response, who may store it, and how to revalidate it.

  • Cache-Control: Core policy, e.g., max-age, s-maxage, public, private, no-cache, no-store, must-revalidate, stale-while-revalidate, stale-if-error.
  • ETag: A fingerprint of the response. Clients send it back via If-None-Match to ask, “Is this still the same?” If yes, server replies 304 Not Modified.
  • Last-Modified: Timestamp of last change. Clients can revalidate with If-Modified-Since.
  • Vary: Declares which request headers affect the response (e.g., Vary: Accept-Language).
  • Expires: Legacy absolute timestamp; typically use Cache-Control instead.

Mental model

Think of cache policy as a recipe card:

  • Who can keep it? public (any cache) or private (only browser).
  • How long? seconds via max-age (browser) and s-maxage (shared caches/CDNs).
  • What if it’s old? must-revalidate vs stale-while-revalidate vs stale-if-error.
  • How to check freshness? Use ETag or Last-Modified for conditional GETs.
  • Does it depend on request headers? Declare with Vary.
Header cheat sheet (open)
  • Cache-Control: public, max-age=86400 — Anyone can cache, for 1 day.
  • Cache-Control: private, no-cache — Only end-user’s browser may store; must revalidate.
  • Cache-Control: no-store — Do not store at all (sensitive data).
  • s-maxage=... — Overrides max-age for shared caches (CDNs/proxies).
  • stale-while-revalidate=... — Serve old content while fetching fresh in background.
  • stale-if-error=... — Serve stale if origin fails.
  • ETag: "abc123" — Strong validator; use with If-None-Match.
  • Last-Modified: Tue, 20 Jan 2026 10:00:00 GMT — Use with If-Modified-Since.
  • Vary: Accept, Accept-Encoding, Accept-Language — Response depends on these headers.

How to choose headers (step-by-step)

  1. Classify data sensitivity: If contains private or regulated data, prefer private or no-store.
  2. Estimate change frequency: Rarely changes → longer max-age; often changes → short max-age + validators.
  3. Decide cache scope: Public assets shared across users → public. Per-user views → private.
  4. Add validators: Provide ETag and/or Last-Modified to enable 304 savings.
  5. Tune shared caches: Use s-maxage, stale-while-revalidate, stale-if-error to improve CDN behavior.
  6. Declare variability: If response changes by header (e.g., locale), set Vary accordingly.
  7. Test with conditional requests: Confirm 200 with body on first request, then 304 on revalidation.

Worked examples

1) Static schema JSON (rare changes)

GET /openapi.json

200 OK
Cache-Control: public, max-age=604800, immutable
ETag: "f1b5c6"
Content-Type: application/json

{ ...large schema... }

Why: Large file, rarely changes. immutable says never revalidate within max-age.

2) Product catalog (updates hourly, shared via CDN)

GET /products?category=shoes

200 OK
Cache-Control: public, max-age=300, s-maxage=3600, stale-while-revalidate=60, stale-if-error=86400
ETag: "p-cat-2026-01-21-10"
Last-Modified: Tue, 21 Jan 2026 10:00:00 GMT
Vary: Accept, Accept-Encoding
Content-Type: application/json

{ "items": [ ... ] }

Why: Browsers keep for 5 minutes; CDNs can keep for 1 hour. During refresh, CDN may serve stale for 60s. If origin fails, serve stale for a day. ETag/Last-Modified enable 304.

3) User dashboard (per-user, avoid shared cache)

GET /me/dashboard

200 OK
Cache-Control: private, no-cache, must-revalidate
ETag: "dash-u123-v7"
Vary: Accept, Accept-Encoding
Content-Type: application/json

{ ...private data... }

Why: Only the user’s browser may store it. no-cache forces revalidation each time; ETag enables 304 without re-sending the body.

4) Localized content (varies by language)

GET /articles/42  (Accept-Language: fr-FR)

200 OK
Cache-Control: public, max-age=86400
Vary: Accept-Language, Accept-Encoding
ETag: "art42-fr-v3"
Content-Type: application/json

{ ...texte en français... }

Why: Prevents English and French versions from overwriting each other in shared caches.

Exercises you can run today

These mirror the exercises below. Try first, then open the solution.

Exercise 1 — Catalog caching policy

Design headers for GET /catalog that updates daily, is safe to share via CDN, and should allow fast revalidation.

Expected behavior
  • Browser can keep a short-lived copy.
  • CDN can keep up to a day.
  • Conditional GET returns 304 when unchanged.

Exercise 2 — Conditional GET with If-Modified-Since

Handle a request for /reports/monthly with If-Modified-Since. If data unchanged since the given date, reply with 304.

  • Checklist
    • Picked correct Cache-Control for scope and freshness.
    • Included ETag or Last-Modified consistently on 200 and 304.
    • Used Vary only when needed.
    • Tested the 304 path with a second request.

Common mistakes and self-check

  • Forgetting validators: No ETag or Last-Modified means no 304 savings. Self-check: Do 200 and 304 both include validators?
  • Overusing no-store: Blocks all caching, even harmless assets. Self-check: Could this be private or no-cache instead?
  • Missing Vary: Localized or compressed variants may collide. Self-check: Does response depend on Accept* headers?
  • Public caching of private data: Leaks across users. Self-check: Any endpoint returning user-specific data must be private or no-store.
  • Not aligning CDN and browser TTLs: Only max-age used, but no s-maxage. Self-check: Are shared caches tuned with s-maxage?
  • Wrong 304 headers: 304 must not include a body but should include validators and cache headers. Self-check: Does your 304 return ETag/Last-Modified and matching Cache-Control?

Who this is for

  • API Engineers and Backend Developers building HTTP services.
  • Engineers integrating CDNs or reverse proxies.
  • Anyone troubleshooting slow endpoints or high origin load.

Prerequisites

  • Comfortable with HTTP basics (methods, status codes, headers).
  • Ability to run local server or API framework of your choice.
  • Awareness of data sensitivity and privacy requirements.

Learning path

  1. Review HTTP caching semantics: freshness vs validation.
  2. Add Cache-Control to static-like endpoints.
  3. Implement ETag generation and conditional GET.
  4. Introduce s-maxage and stale directives for CDN paths.
  5. Define Vary rules for content negotiation (locale, encoding, format).
  6. Set policies for private and sensitive endpoints.
  7. Measure: check hit ratios, latency, and origin load; adjust TTLs.

Practical projects

  • Write a small middleware that computes strong ETags from response bodies (e.g., SHA-256 of JSON) and supports If-None-Match.
  • Add Last-Modified based on database updated_at; respond 304 for If-Modified-Since.
  • Apply public, s-maxage, and stale-while-revalidate for a read-heavy list endpoint behind a CDN; measure origin traffic reduction.
  • Audit endpoints to mark each as public, shared or private; propose specific headers for each.

Mini challenge

Pick one endpoint that currently has no caching headers. Propose a safe policy, add ETag or Last-Modified, and verify a 304 path with a second request. Aim for a 20% reduction in transferred bytes.

Next steps

  • Implement validators on at least two endpoints.
  • Tune CDN behavior with s-maxage and stale directives where safe.
  • Document your cache matrix (endpoint → policy) for your team.

Quick Test

The Quick Test is available to everyone. Sign in to save your progress and track results over time.

Practice Exercises

2 exercises to complete

Instructions

You own GET /catalog: JSON list of categories and counts. Updates once per day at midnight UTC. It’s safe to serve to all users via CDN. Write response headers for success (200) and for 304 when unchanged. Include both browser and CDN-friendly directives and a validator.

Expected Output
200 OK with Cache-Control allowing browser and stronger CDN TTL, an ETag and/or Last-Modified; 304 Not Modified with matching Cache-Control and same validators, no body.

Caching Headers And ETags — Quick Test

Test your knowledge with 8 questions. Pass with 70% or higher.

8 questions70% to pass

Have questions about Caching Headers And ETags?

AI Assistant

Ask questions about this tool