1. Library
  2. Computer Networks
  3. Http and the Web
  4. Headers

Updated 9 hours ago

Every time your browser fetches a resource, it's asking the same question: do I already have this?

If yes, it can skip the network entirely—no latency, no bandwidth, instant response. If no, it waits. The difference between a 100-millisecond page load and a 3-second one often comes down to how well you've answered that question in advance.

Cache-Control is how you answer it.

The Core Idea

Caching is a bet on time. When you set max-age=3600, you're saying: "This response will remain true for the next hour. Trust it." When you set max-age=31536000, you're saying: "This will be true for a year."

Every max-age value is a bet: how long will this truth remain true?

Get it right, and your application feels instant. Get it wrong, and users see stale data—or worse, you waste bandwidth re-fetching content that hasn't changed.

The Syntax

Cache-Control appears in HTTP responses (and sometimes requests). Multiple directives combine with commas:

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

Each directive answers a different question about how to cache.

Who Can Cache? (public vs. private)

public: Anyone can cache this—browsers, CDNs, corporate proxies, your ISP.

Cache-Control: public, max-age=86400

Use for content that's identical for all users: images, stylesheets, JavaScript, fonts.

private: Only the end user's browser can cache this. Shared caches must not store it.

Cache-Control: private, max-age=3600

Use for user-specific content: dashboards, account pages, personalized API responses.

The distinction matters for security. A CDN caching your user's personal data and serving it to someone else is a breach. private prevents that.

How Long? (max-age and s-maxage)

max-age sets freshness lifetime in seconds:

Cache-Control: max-age=3600

Common values:

  • 60 — one minute (rapidly changing content)
  • 3600 — one hour (moderately dynamic)
  • 86400 — one day (daily updates)
  • 604800 — one week
  • 31536000 — one year (versioned static assets)

s-maxage overrides max-age for shared caches only:

Cache-Control: max-age=60, s-maxage=3600

Browsers cache for one minute; CDNs cache for an hour. Useful when you want CDN efficiency but browser freshness.

The Confusingly Named Directives

no-cache doesn't mean "don't cache." It means "cache it, but ask me before using it."

Cache-Control: no-cache

The cache stores the response but validates with the server before serving it. If unchanged, the server says "304 Not Modified" and the cache serves its copy. If changed, fresh content arrives.

This gives you freshness guarantees while still benefiting from caching when nothing has changed.

no-store actually means don't cache:

Cache-Control: no-store

Nothing gets stored. Every request hits the origin. Use for sensitive data: banking transactions, medical records, passwords.

Revalidation Directives

must-revalidate: Once stale, always revalidate. Don't serve stale content even if the server is unreachable.

Cache-Control: max-age=3600, must-revalidate

Without this, some caches might serve stale content when they can't reach the origin. With it, they return an error instead.

immutable: This content will never change. Don't bother revalidating, ever.

Cache-Control: max-age=31536000, immutable

Perfect for versioned assets like app.v2.4.1.js. The filename changes when content changes, so the URL is effectively a content address. Browsers can skip revalidation entirely, even on refresh.

How Validation Works

When cached content goes stale, the browser asks: "Is this still good?"

Two mechanisms make this efficient:

ETags are content fingerprints:

# Response
ETag: "a1b2c3d4"
Cache-Control: max-age=3600

# Revalidation request
If-None-Match: "a1b2c3d4"

# If unchanged: 304 Not Modified (no body)
# If changed: 200 OK with new content and new ETag

Last-Modified uses timestamps:

# Response
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

# Revalidation request
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT

ETags are more precise—they can be based on content hashes. Last-Modified has one-second granularity and can't detect sub-second changes.

Practical Strategies

Versioned Static Assets

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

Filename: styles.a8f3b2.css

Cache forever. When content changes, the filename changes, and browsers fetch the new URL. This is the gold standard for static assets.

HTML Documents

Cache-Control: no-cache

HTML references other resources. If it's cached too long, users might see old HTML pointing to old assets. Require validation to ensure freshness.

API Responses

For data that can tolerate some staleness:

Cache-Control: private, max-age=300

Five-minute caching reduces server load significantly.

For real-time data:

Cache-Control: no-cache

For sensitive data:

Cache-Control: no-store

User-Uploaded Images

Cache-Control: public, max-age=604800

One-week caching works well for content that changes rarely.

Common Mistakes

Caching error responses: A 500 error cached for an hour means an hour of broken experience. Errors should use no-store.

Using public for user-specific content: This can leak private data through shared caches. Always use private for personalized responses.

Thinking no-cache prevents caching: It doesn't. It requires validation. For no caching, use no-store.

No Cache-Control at all: Without explicit headers, caching behavior varies unpredictably across browsers and proxies. Always be explicit.

Debugging

In browser DevTools (Network tab), the Size column reveals cache status:

  • A number like "45.2 KB": fetched from network
  • "(disk cache)": served from disk
  • "(memory cache)": served from memory

Click any request to inspect its Cache-Control headers.

The "Disable cache" checkbox bypasses caching during development—useful for testing changes without clearing your cache.

Frequently Asked Questions About Cache-Control Headers

Was this page helpful?

😔
🤨
😃