Last modified: April 27, 2026

This article is written in: πŸ‡ΊπŸ‡Έ

HTTP Caching

HTTP caching is the process of storing copies of HTTP responses so that future requests can be served without contacting the origin server. It operates at multiple layers β€” the browser, intermediate proxies, reverse proxies, and CDN edge nodes β€” and is controlled primarily through standardised HTTP headers. Correctly configured HTTP caching can dramatically reduce latency, lower server load, and improve the experience for end users on slow or expensive connections.

HTTP Caching Layers

  +----------+        +----------+        +----------+        +----------+
  | Browser  |        | Forward  |        | Reverse  |        |  Origin  |
  | Cache    |<------>| Proxy    |<------>| Proxy /  |<------>|  Server  |
  | (client) |        | (ISP/org)|        |   CDN    |        |          |
  +----------+        +----------+        +----------+        +----------+
      |                    |                   |                    |
   Private             Shared             Shared              Authoritative
   per user           per org           per region              source

  Each layer stores a cached copy keyed on the request URL (and Vary fields).
  A cache HIT at any layer avoids a round-trip to every layer to its right.

Cache-Control Header

Cache-Control is the primary HTTP header for specifying caching directives. It appears in both request and response messages.

Example response with Cache-Control:

  HTTP/1.1 200 OK
  Content-Type: application/json
  Cache-Control: public, max-age=3600, stale-while-revalidate=60
  ETag: "v3-abc123"
  Last-Modified: Sat, 26 Apr 2026 06:00:00 GMT

  { "id": 7, "name": "Widget" }

Response Directives

Directive Effect
public Response may be stored by any shared cache
private Response is only for the requesting user; shared caches must not store it
no-cache Cache must revalidate with origin before serving a stored response
no-store Cache must not store the response at all
max-age=N Response is fresh for N seconds from the time it was generated
s-maxage=N Overrides max-age for shared caches (CDNs, proxies)
must-revalidate Once stale, cache must not serve the response without successful revalidation
proxy-revalidate Like must-revalidate but applies only to shared caches
immutable Response will not change during its freshness lifetime; browsers skip revalidation
stale-while-revalidate=N Serve stale response for up to N seconds while fetching a fresh one in the background
stale-if-error=N Serve stale response for up to N seconds if origin returns an error

Request Directives

Directive Effect
no-cache Force revalidation even if cache has a fresh copy
no-store Do not cache the response to this request
max-age=0 Treat any cached copy as stale
max-stale=N Accept a response that has been stale for at most N seconds
min-fresh=N Require the response to be fresh for at least another N seconds
only-if-cached Return a stored response or a 504; never contact the origin

Freshness and Staleness

A cached response transitions from fresh to stale after its freshness lifetime expires.

Freshness Lifetime Calculation

  Response stored at t=0
  max-age = 3600 s (1 hour)

  t=0       t=1800        t=3600      t=4200
  |---------|-------------|-----------|-------->
  |< fresh (serve without revalidation) >|stale|

  Age header = current_time - Date header value
  Fresh if Age < max-age

Conditional Requests and Revalidation

When a cached response becomes stale, the cache can ask the origin whether the content has changed rather than fetching the full body.

Revalidation Flow (ETag)

  Client/Cache           Origin Server
      |                       |
      |-- GET /api/user ------>|
      |   If-None-Match: "v3" |
      |                       |
      |<-- 304 Not Modified --|  (no body, headers only)
      |   ETag: "v3"          |
      |                       |
      Cache is refreshed and   |
      freshness timer resets.  |
      Response served from     |
      local copy.              |

Vary Header

The Vary header instructs caches to store separate copies of a response based on the value of specified request headers.

Vary: Accept-Encoding

  Request 1: Accept-Encoding: gzip   -> cached under key (URL + gzip)
  Request 2: Accept-Encoding: br     -> cached under key (URL + br)
  Request 3: Accept-Encoding: (none) -> cached under key (URL + identity)

  Three separate cache entries for the same URL.

Pragma and Expires (Legacy)

Browser Caching

Browsers maintain their own private cache stored on the user's disk or in memory.

Browser Cache Decision Tree

  New navigation to URL
         |
         v
   Check memory cache
         |
   HIT --+-- MISS
    |              |
    v              v
  Serve       Check disk cache
  immediately       |
              HIT --+-- MISS
               |            |
               v            v
         Check freshness  Fetch from
               |           network
         FRESH-+-STALE
           |          |
           v          v
         Serve    Revalidate
         copy     (conditional
                   request)

Cache Busting

When content changes but the URL stays the same, stale cached copies continue to be served until they expire.

HTTP/2 and HTTP/3 Caching

Security Considerations

Best Practices