HTTP Cache Headers Explained

What are http cache headers?

HTTP cache headers are part of the HTTP protocol and control how web browsers and intermediary caches (like CDNs or proxy servers) store and reuse responses. They help improve performance, reduce bandwidth usage, and minimize server load by avoiding unnecessary requests for resources that haven’t changed.

Here’s a breakdown of the key HTTP cache headers:


1. Cache-Control (most important)

This header tells the browser or intermediary how, and for how long, the response can be cached.

Examples:

  • Cache-Control: no-cache – the resource must be revalidated with the server before using it.

  • Cache-Control: no-store – the response must not be stored in any cache.

  • Cache-Control: public, max-age=3600 – the response can be cached by any cache and is fresh for 3600 seconds.

  • Cache-Control: private, max-age=600 – only the client browser can cache the resource for 600 seconds.


2. Expires

This is an older header (mostly replaced by Cache-Control) that specifies a date/time after which the response is considered stale.

Example:

  • Expires: Wed, 21 Oct 2025 07:28:00 GMT

If both Cache-Control and Expires are present, Cache-Control takes precedence.


3. ETag (Entity Tag)

Used for conditional requests. It's a fingerprint (hash) of the response. When the browser makes a subsequent request, it includes the ETag via the If-None-Match header. The server compares it and returns:

  • 304 Not Modified if the content hasn’t changed.

  • 200 OK with the new content if it has changed.

Example:

  • ETag: "abc123"


4. Last-Modified

Specifies the last time the resource was changed. Works similarly to ETag.

  • Browser sends If-Modified-Since.

  • Server responds with 304 Not Modified if unchanged.

Example:

  • Last-Modified: Tue, 10 Jun 2025 10:00:00 GMT


Summary Table

Header Purpose
Cache-Control Main directive for how caching works
Expires Sets expiration date/time (older standard)
ETag Validates if resource has changed
Last-Modified Another way to validate if resource changed

Would you like a real-world example showing how these are used in an HTTP response?

Are these only valid till the browser is closed?

Great question — the validity of HTTP cache headers is not limited to just the browser session. It depends on how the resource is cached and what directives are used. Here's how it breaks down:


🔒 1. Session-only Cache (In-Memory)

  • Cached only in RAM.

  • Lost when the browser is closed.

  • Used for resources with no persistent caching policy or certain Cache-Control headers like no-store.


📦 2. Persistent Cache (Disk Cache)

  • Stored on disk, so it persists across browser sessions.

  • Resources with headers like:

    http
    Cache-Control: public, max-age=86400

    can be stored in disk cache and reused even after the browser is closed and reopened, as long as they’re still "fresh" (within max-age).


✅ When Do Cache Headers Persist Across Sessions?

Header / Directive Persists After Browser Close? Notes
Cache-Control: max-age=... ✅ Yes If stored to disk.
Cache-Control: no-store ❌ No Not cached at all.
Cache-Control: no-cache 🔄 Needs revalidation Cache exists but must revalidate.
ETag, Last-Modified ✅ Yes Used for revalidation (can persist).
Expires ✅ Yes Acts like max-age.

💡 Summary

  • Persistent caching (on disk) means resources survive browser restarts, as long as the cache headers allow it.

  • In-memory caching (like some no-store or private resources) vanishes once the browser is closed.

If you want to ensure that resources don’t persist after closing the browser, you should use:

http
Cache-Control: no-store

Or if you want them to persist (e.g., for performance), use:

http
Cache-Control: public, max-age=86400

Would you like to see how a specific browser (like Chrome) handles this under the hood?

what is the usage of no-cache?

The Cache-Control: no-cache directive is often misunderstood, so here's a clear explanation:


🔄 What does no-cache actually mean?

Contrary to how it sounds, no-cache does not prevent caching.

Instead, it means:

✅ You can store the response in cache,
❌ But you must revalidate it with the server before using it again.


🔍 Real-world behavior

When a browser caches a resource with no-cache, it still stores it (on disk or in memory), but every time that resource is requested again, the browser sends a conditional request to the server with headers like:

http
If-None-Match: "abc123" ← uses ETag If-Modified-Since: Tue, 10 Jun 2025 10:00:00 GMT

If the resource hasn’t changed, the server replies:

http
304 Not Modified

And the browser can use the cached copy.


📦 Why use no-cache?

no-cache is useful when:

  • You want to avoid stale content but still save bandwidth.

  • You expect content might change often, but not every time.

  • You want controlled freshness with validation.


🆚 Comparison with other cache directives

Directive Stored? Revalidated? Used without check?
no-cache ✅ Yes ✅ Yes ❌ No
no-store ❌ No ❌ No ❌ No
max-age=600 ✅ Yes ❌ No (until expiry) ✅ Yes

✅ Example use case

For a logged-in user dashboard, you might use:

http
Cache-Control: private, no-cache, must-revalidate
  • private → only store in user's browser.

  • no-cache → must revalidate.

  • must-revalidate → ensures the revalidation always happens even when offline.


Let me know if you want an example response with all the headers combined or how to configure this in a specific framework like Express.js or Nginx.

So the no-cache won't validate when offline by default?

Correct — no-cache does not allow use of the cached resource when offline by default.

Here’s what happens:


📵 When you're offline and Cache-Control: no-cache is set:

  • The browser wants to revalidate the cached response with the server.

  • But since it can’t reach the server, the validation fails.

  • Result: The resource is not used, and you get an error (like a failed network request).


🔁 Why?

Because no-cache explicitly tells the browser:

"You must check with the server before using this."

So if the browser can't check (because you're offline), it refuses to use even a previously cached copy.


🧠 Related tip: must-revalidate

This makes the behavior stricter. With must-revalidate, even if the cache is expired and you're online, the browser won’t fall back to using stale content.

Without must-revalidate, some browsers might use stale cache under certain edge cases — like being temporarily offline.


✅ If you want offline support:

Use Cache-Control with a longer max-age, or design a Service Worker (for full offline-first behavior).

Example:

http
Cache-Control: public, max-age=86400

This tells the browser: "You can use this resource for up to 1 day without checking back."


Would you like a code snippet or example of configuring this for offline use (e.g., in a PWA or Service Worker)?

What is the difference between public and private?

Great question — the public and private directives in Cache-Control are about who is allowed to cache the response.


🔓 public

Meaning:

"This response can be cached by anyone — including shared caches like CDNs, proxy servers, and browsers."

Use case:
For static assets or non-user-specific content (e.g., CSS, JS, images, public pages).

Example:

http
Cache-Control: public, max-age=86400

→ Cacheable by browser and intermediary caches (like Cloudflare, Varnish, etc.) for 1 day.


🔒 private

Meaning:

"This response is intended for a single user, and must only be cached by their browser — not by shared caches."

Use case:
For user-specific data, such as account pages, dashboards, or personalized content.

Example:

http
Cache-Control: private, max-age=600

→ Cached by the browser only, and only for 10 minutes. Intermediary caches must not store it.


🚫 No public or private?

If neither is specified, behavior varies:

  • Some caches may treat it as cacheable, others may not — depends on headers like Authorization and the HTTP method used.

  • It's safer to explicitly set one of them for predictable behavior.


🧪 Quick comparison table:

Directive Cacheable by Browser Cacheable by Shared Caches (CDNs, Proxies) Typical Use Case
public ✅ Yes ✅ Yes CSS, JS, images, APIs for all users
private ✅ Yes ❌ No User profiles, account info
(None) 🤷‍♂️ Maybe 🤷‍♀️ Maybe Risky — depends on other headers

Let me know if you'd like to see how this works in browser DevTools or how to set this up in a web server like Nginx or Express.