---
title: "How to Configure Cache-Control Without Breaking Your Website"
description: "A practical Cache-Control guide with safe patterns for static assets, HTML, APIs, private data, shared CDNs, revalidation, and stale responses."
canonical_url: https://optimi.com/en/guides/cache-control
md_url: https://optimi.com/en/guides/cache-control.md
last_updated: 2026-07-14
---

# How to Configure Cache-Control Without Breaking Your Website

Choose directives by audience and freshness requirements, then verify the effective policy in browsers and shared caches.

To configure `Cache-Control` safely, decide whether a response is public or private, whether it may be stored, how long it stays fresh, and whether stale content is acceptable. Then emit a small, explicit policy from the origin and check whether your CDN preserves or overrides it. The most important distinction is that `no-cache` allows storage but requires revalidation, while `no-store` tells caches not to store the response.

`Cache-Control` is an HTTP response header interpreted by browsers and shared caches such as CDNs and proxies. Cloudflare, Fastly, and equivalent providers may add an edge TTL, cache rule, or surrogate policy, so the header at the origin is the starting policy, not necessarily the final behavior.

## The directives you need most

### `public`, `private`, and `no-store`

`public` says a response may be stored by shared caches. `private` limits storage to a private cache, such as the user's browser, and is appropriate for personalized responses. `no-store` asks caches of any kind not to store the response. Use it for sensitive data, credentials, payment steps, or responses whose retention is unacceptable.

### `max-age` and `s-maxage`

`max-age` gives the freshness lifetime in seconds for a response. `s-maxage` applies to shared caches and overrides `max-age` there, while browsers ignore it. Together they let you give a browser and a CDN different freshness windows:

```http
Cache-Control: public, max-age=60, s-maxage=600
```

Use integer seconds. Keep the window short when content changes often, and use versioned URLs for content that is genuinely immutable.

### `no-cache` and validators

`no-cache` means that a stored response must be validated before reuse. It does not mean "do not cache." Pair it with `ETag` or `Last-Modified` so the validator can produce an efficient `304 Not Modified` response:

```http
Cache-Control: public, no-cache
ETag: "homepage-release-42"
```

This pattern is useful for HTML that should be checked on each navigation but does not need to be downloaded in full when unchanged.

### `immutable`

`immutable` tells clients that a fresh response will not change. Use it only for resources whose URL changes when their bytes change:

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

Never combine a long immutable lifetime with a mutable filename such as `/styles.css` unless you have a reliable versioning and purge strategy that prevents old references from remaining in circulation.

### `stale-while-revalidate` and `stale-if-error`

`stale-while-revalidate` allows a cache to serve a stale response for a limited window while it revalidates in the background. `stale-if-error` allows stale content when the origin fails. They can improve resilience, but they extend the period in which old data may be seen. Use them only for content where that tradeoff is explicit.

> **Stale is a product decision**
>
> Serving an old article may be harmless; serving an old price, stock level, permissions response, or account balance may not be. Set stale windows per content class, not globally.

**Choose Cache-Control from audience and freshness requirements**

![Decision flow that first separates sensitive or personalized responses from shareable public responses, then directs immutable versioned assets to a long immutable policy, regularly changing public content to a bounded shared lifetime with validators, and responses requiring validation before reuse to no-cache with an ETag.](/diagrams/cache-control/policy-selection.svg)

*A small response-class policy is safer than one global TTL: private data is not stored in shared caches, while public representations receive only the reuse semantics they can support.*

## Safe policy patterns

### Fingerprinted static assets

For hashed JavaScript, CSS, fonts, and media:

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

The build must publish a new URL when the content changes. If a deployment keeps the same URL, choose a shorter lifetime and a tested purge process instead.

### Public HTML that changes regularly

For public HTML that can be briefly fresh at the edge but should update in browsers more often:

```http
Cache-Control: public, max-age=60, stale-while-revalidate=30
ETag: "page-2026-07-14-8"
```

This is a starting example, not a universal TTL. Check the actual publish frequency, user expectations, and the provider's stale semantics. If the shared cache needs a different freshness lifetime, use `s-maxage` as a separate policy and verify its interaction with stale directives. A CDN rule that forces a different edge TTL can override the origin's intent.

### Public API data

For a read-only response that is identical for every anonymous request:

```http
Cache-Control: public, max-age=30, s-maxage=120
ETag: "products-987"
```

Include the parameters that change the result in the cache key. Do not strip a query parameter merely because it looks optional. Verify pagination, filters, locale, currency, and authorization behavior.

### Personalized or sensitive responses

For account or checkout data:

```http
Cache-Control: private, no-store
```

If a non-sensitive personalized response can be retained by the browser but must not be reused by a shared CDN, `private, max-age=60` may be appropriate after security review. Do not use a public cache rule to compensate for an ambiguous application response.

## Headers that affect correctness

`Vary` tells caches which request headers select a different representation. If a response changes by language or encoding, make that variation explicit and confirm that the CDN honors it. Varying on a high-cardinality header, especially the whole `Cookie` value, can destroy hit ratio. Omitting a required variation can serve the wrong language, format, or device version.

`Set-Cookie` and `Authorization` require special attention. A session cookie often indicates personalization. A request with `Authorization` should not be served a shared response unless you have a deliberate, secure policy and the cache implementation follows it. If in doubt, use `private` or `no-store` and redesign the public/private boundary.

`ETag` and `Last-Modified` support conditional requests. Keep them stable for the same representation and change them when the representation changes. `Content-Encoding` and content negotiation must match the representation delivered to the client; test compressed and uncompressed paths.

## Configure the edge without losing the origin contract

Treat the origin response as the source policy and the CDN as an enforcement layer. If Cloudflare, Fastly, or another provider uses an edge cache TTL, a cache rule, `Surrogate-Control`, or a provider-specific header, document its precedence. A setting that forces caching can defeat `private` or `no-store`; a setting that forces bypass can make a correct origin policy look ineffective.

Inspect both the origin and public responses. Compare a direct origin request with a request through the CDN, then test a cache miss, hit, revalidation, purge, and origin error. Never rely on a dashboard label alone.

## Mistakes that break websites

Do not use `no-cache` when the requirement is no storage. Do not use `no-store` everywhere and then wonder why repeat navigations and browser back-forward behavior are slow. Do not cache HTML without checking cookies and authorization. Do not add `immutable` to a mutable URL. Do not set `s-maxage` without considering how it interacts with stale behavior at your provider. Do not forget that a purge cannot reach a browser cache that still considers an object fresh.

## Validation checklist

For each response class, document its audience, cacheability, browser TTL, shared-cache TTL, validator, cache key, stale policy, and purge owner. Use `curl -I` and browser DevTools to inspect `Cache-Control`, `Age`, `ETag`, `Last-Modified`, `Vary`, `Set-Cookie`, status, and cache status. Test anonymous, authenticated, localized, and query-parameter variants. Confirm a changed response becomes visible within the promised window and that an origin error cannot expose sensitive or dangerously stale data.

For the wider cache design, read [CDN caching explained](/en/guides/cdn-caching). For reducing the origin delay behind a miss, see [How to reduce TTFB](/en/guides/reduce-ttfb), [managed CDN delivery](/en/performance/cdn), and [managed DNS](/en/performance/dns).

> There are only two hard things in Computer Science: cache invalidation and naming things.

[Contact Us](/en/contact): Review your caching policy before it becomes an incident — Talk to our team about aligning origin headers, CDN rules, freshness, and invalidation with your application behavior.
