← Technology Archive

Historical archive

HTTP Cache Header Priority: Cache-Control, ETag, and Last-Modified

How browser freshness caching and conditional requests interact, including the precedence of Cache-Control, Expires, ETag, and Last-Modified.

Browser caching can be divided into two broad mechanisms: freshness caching and validation, also called conditional caching.

Freshness caching

After the first request, the browser can reuse a local response while it remains fresh without contacting the server. The main headers are:

  • Expires: <date>
  • Cache-Control: max-age=<seconds>
  • Cache-Control: no-cache
  • Cache-Control: no-store

When both Expires and Cache-Control are present, Cache-Control takes precedence. Older advice often included both because Cache-Control was introduced in HTTP/1.1 while Expires existed in HTTP/1.0.

Conditional validation

Once validation is required, the browser contacts the server. If the representation has not changed, the server returns 304 Not Modified, allowing the cached body to be reused. Otherwise, it returns the new content.

  • The server sends ETag: "<value>", and the client later sends If-None-Match: "<value>". A match can produce a 304 response.
  • The server sends Last-Modified: <date>, and the client later sends If-Modified-Since: <date>. If the resource has not changed since that time, the server can return 304.

Precedence

  • A still-fresh response is reused before conditional validation is considered.
  • Cache-Control takes precedence over Expires.
  • ETag generally takes precedence over Last-Modified when both validators are available.
  • The older HTTP/1.0 Pragma header can affect caching, but it is rarely appropriate for modern response configuration.

Practical use

Bundlers commonly add content hashes to CSS and JavaScript filenames. Those immutable assets are good candidates for long-lived freshness caching because a content change creates a new URL. Images without hashes and HTML documents often need revalidation instead.

Browser behavior can differ when only validators are configured. One approach is Cache-Control: public, max-age=0, which allows shared caches while requiring immediate validation. Another is a very short positive max-age, which allows a brief fresh period before validation.

These notes reflect browser behavior observed when the original article was written. For production systems, review current HTTP caching specifications and CDN behavior.