Try the HTTP Headers

HTTP Caching Deep Dive: Cache-Control Directives, ETags, and Content Hashing Strategy

Content-hashed filenames with max-age=31536000 can eliminate network requests for returning users. Here's the complete HTTP caching strategy: Cache-Control directives in depth, ETags and conditional requests, the Vary header pitfall, CDN cache busting, and a practical caching strategy by resource type.

By sadiqbd Β· June 9, 2026

Share:
HTTP Caching Deep Dive: Cache-Control Directives, ETags, and Content Hashing Strategy

HTTP caching is the single highest-leverage performance optimisation available β€” and most sites implement it incorrectly

A page that loads 2 seconds on the first visit but instantly on return visits is a better experience than a page that loads 1.5 seconds on every visit. Browser caching, CDN caching, and server-side caching together can eliminate the majority of network requests for returning users β€” but only when Cache-Control directives, ETags, and the Vary header are configured correctly.


The caching layers and what controls each

Browser cache: stores responses locally on the user's device. Controlled by Cache-Control headers. Re-requests happen based on max-age, ETags, and Last-Modified values.

CDN cache (shared/proxy cache): servers geographically distributed between origin servers and end users. Stores responses for all users. Also controlled by Cache-Control headers, specifically directives that distinguish public from private content.

Service worker cache: JavaScript-controlled cache that intercepts network requests. Can cache anything independently of HTTP headers. Used for offline capabilities and progressive web apps.


Cache-Control directives in depth

The Cache-Control header controls caching behaviour. Multiple directives can be combined:

Cache-Control: public, max-age=31536000, immutable

public: the response can be stored by any cache β€” browser and CDN. Default for non-authenticated responses.

private: only the user's browser can cache. CDNs must not store private responses. Required for personalised content (user-specific HTML, authenticated API responses).

max-age=N: the response is fresh for N seconds. After this, the cache must revalidate before serving.

s-maxage=N: like max-age but applies only to shared caches (CDNs). Allows different TTLs for browser vs. CDN:

Cache-Control: public, max-age=60, s-maxage=86400

This instructs browsers to cache for 1 minute (so users see fresh content relatively quickly) while allowing CDNs to cache for 24 hours (reduces origin load).

immutable: tells the browser not to revalidate the resource during its freshness period, even after a hard refresh. Used with content-hashed URLs where the URL changes when the content changes β€” there's no need to check for updates if the URL is the same (same URL = same content).

no-cache: the response can be cached, but must be revalidated with the origin before being served. This is NOT the same as no-store.

no-store: don't cache this at all. Used for sensitive data (banking transactions, medical records).

must-revalidate: once max-age expires, the cache must check with the origin. It cannot serve stale content.

stale-while-revalidate=N: serve stale content immediately while refreshing in the background. Improves perceived performance at the cost of potentially serving slightly outdated content.


Conditional requests: ETags and Last-Modified

When a cached resource expires, the browser doesn't necessarily download the full response again β€” it sends a conditional request to check if the resource has changed.

ETag-based validation:

  1. Server sends: ETag: "abc123def456"
  2. Browser stores the ETag alongside the cached response
  3. On next request (after max-age expires): browser sends If-None-Match: "abc123def456"
  4. If content unchanged: server responds 304 Not Modified (no body, saves bandwidth)
  5. If content changed: server sends new response with new ETag

Last-Modified-based validation:

  1. Server sends: Last-Modified: Mon, 09 Jun 2025 10:00:00 GMT
  2. On next request: browser sends If-Modified-Since: Mon, 09 Jun 2025 10:00:00 GMT
  3. If unchanged: 304 Not Modified

ETags are more reliable (don't depend on clock accuracy) and are preferred. ETags can be "strong" (content-based, "abc123") or "weak" (semantically equivalent but not byte-identical, W/"abc123").


Cache busting with content hashing

The challenge: max-age=31536000 caches a file for a year, but what if the file changes? Users who have it cached will see the old version.

The solution: change the URL when the content changes. Build tools (webpack, Vite, Parcel) hash file content and include the hash in filenames:

app.a7f3bc91.js    β†’ if app.js changes β†’    app.cd48e213.js
styles.4b9a2f11.css  β†’ if styles.css changes β†’ styles.7e3d1ab2.css

Now you can safely use max-age=31536000, immutable β€” cached files are valid forever because the URL is permanently tied to specific content. When files update, the HTML that references them changes (new hash in the filename), causing the browser to fetch the new file.

HTML and API responses shouldn't use long caching β€” they need to be fresh because they reference the latest hashed asset URLs. Typical approach:

# Static assets (content-hashed filenames)
Cache-Control: public, max-age=31536000, immutable

# HTML, API responses
Cache-Control: no-cache

The Vary header

The Vary header tells caches that the response may differ based on certain request headers. The cache stores separate versions for each variation.

Vary: Accept-Encoding

This tells caches: "I might send gzip, brotli, or uncompressed responses depending on what the client accepts. Cache them separately."

Vary: Accept-Language

"I return different language versions. Cache separately per language."

The Vary: Cookie problem: if you add Vary: Cookie, every distinct cookie value produces a separate cache entry β€” practically bypassing the CDN cache for authenticated users. Most sites want to cache public content while bypassing cache for authenticated requests. This is typically handled by stripping cookies in the CDN before checking the cache for public URLs.


CDN cache headers in practice

CDN services (Cloudflare, CloudFront, Fastly) respect Cache-Control but also add their own diagnostic headers:

Cloudflare:

CF-Cache-Status: HIT      # served from Cloudflare's cache
CF-Cache-Status: MISS     # not in cache, fetched from origin
CF-Cache-Status: BYPASS   # Cloudflare bypassed cache (e.g., request had cookie)
CF-Cache-Status: EXPIRED  # was in cache but expired, re-fetched

AWS CloudFront:

X-Cache: Hit from cloudfront
X-Cache: Miss from cloudfront

These headers are visible in the HTTP Headers checker β€” use them to verify your CDN caching is working as intended.


A practical caching strategy

For a typical web application:

Resource type Cache-Control Cache duration
HTML pages no-cache Revalidated every request
API responses (public) public, max-age=60, s-maxage=300 1 min browser, 5 min CDN
API responses (authenticated) private, no-cache No CDN cache
CSS, JS (content-hashed) public, max-age=31536000, immutable 1 year
Images (content-hashed) public, max-age=31536000, immutable 1 year
Images (no hash) public, max-age=86400 1 day
Fonts public, max-age=31536000, immutable 1 year

How to use the HTTP Headers Checker on sadiqbd.com

  1. Enter your URL β€” any resource on your site
  2. Check the Cache-Control header β€” does it match your intended strategy?
  3. Look for CDN cache status headers β€” is the CDN actually caching?
  4. Check for ETags and Last-Modified β€” are conditional requests supported?
  5. Identify misconfiguration β€” common issues: no-cache on static assets, missing cache headers entirely, private on public resources

Frequently Asked Questions

What's the difference between no-cache and no-store? no-cache means: you can cache this, but check with the server before using the cached copy. If the server says nothing changed, use the cache (saves bandwidth). no-store means: don't cache this at all. Use no-cache for HTML where you want conditional request efficiency; use no-store for truly sensitive data.

Why does my CSS still load slowly even with max-age set? Check whether the cache header is actually being served β€” some CDN configurations override origin headers. Also check: is the URL content-hashed? Without hashing, you might be serving a different file than expected.

Is the HTTP Headers Checker free? Yes β€” completely free, no sign-up required.


HTTP caching is the performance investment that compounds β€” each correctly cached asset is one fewer request for every returning user, indefinitely. Getting the Cache-Control strategy right once, with content hashing for static assets, produces durable performance benefits.

Try the HTTP Headers Checker free at sadiqbd.com β€” inspect Cache-Control, ETags, CDN status headers, and all response headers for any URL instantly.

Share:
Try the related tool:
Open HTTP Headers

More HTTP Headers articles