# Cache-Control Header — Syntax, Values & Examples

> Directives that tell browsers and CDNs whether, where and for how long a response may be cached.

Cache-Control is the main caching header. On a response, it says who may store the response and how long it stays fresh before it must be revalidated. On a request, it lets the client ask caches to revalidate or bypass stored copies — which is what browsers send on a hard reload.

- Type: Request & response header
- Category: Caching
- Defined in: [RFC 9111 §5.2 — Cache-Control](https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2)

## Syntax

```http
Cache-Control: <directive>[=<value>], <directive>…
```

## Values

| Value | Meaning |
|---|---|
| `max-age=<seconds>` | fresh for this many seconds in any cache |
| `s-maxage=<seconds>` | freshness for shared caches (CDNs) only; overrides max-age there |
| `public` | any cache may store it, even for authenticated requests |
| `private` | only the user's browser may store it, not CDNs |
| `no-cache` | may be stored, but must be revalidated before every use |
| `no-store` | must not be stored anywhere |
| `must-revalidate` | once stale, never serve without revalidating |
| `immutable` | will never change — don't revalidate, even on reload |
| `stale-while-revalidate=<seconds>` | serve a stale copy for this long while refreshing in the background |

## Examples

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

fingerprinted static assets such as `app.3f2a1c.js`

```http
Cache-Control: no-cache
```

HTML: always revalidate, so deploys show up on the next load

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

personal or sensitive data

```http
Cache-Control: public, s-maxage=60, stale-while-revalidate=300
```

API responses cached briefly on a CDN

## Good to know

- `no-cache` means 'revalidate before use', not 'don't cache'. Use `no-store` to keep a response out of caches.
- Without Cache-Control, browsers guess a freshness lifetime from `Last-Modified` — always set it explicitly on HTML.
- `s-maxage` only affects shared caches; CDNs such as Cloudflare and Vercel honour it.
- `Expires` is the HTTP/1.0 predecessor; when both are present, `max-age` wins.

## How to set Cache-Control

### Express

```
// Fingerprinted assets: cache for a year
app.use("/assets", express.static("dist/assets", { maxAge: "1y", immutable: true }));

// HTML and API responses: always revalidate
res.set("Cache-Control", "no-cache");
```

### Next.js (route handler)

```
return Response.json(data, {
  headers: { "Cache-Control": "public, s-maxage=60, stale-while-revalidate=300" },
});
// Next.js already sends long-lived immutable caching for /_next/static files.
```

### nginx

```
location /assets/ {
  add_header Cache-Control "public, max-age=31536000, immutable" always;
}

location / {
  add_header Cache-Control "no-cache" always;
}
```

### Apache (.htaccess)

```
<FilesMatch "\.(js|css|woff2|png|jpg|svg)$">
  Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
<FilesMatch "\.html$">
  Header set Cache-Control "no-cache"
</FilesMatch>
```

### fetch (request)

```
// Ask caches along the way to revalidate
await fetch("/api/data", { headers: { "Cache-Control": "no-cache" } });

// Or control the browser's own HTTP cache for this request
await fetch("/api/data", { cache: "no-store" });
```

## Check it

Fetch only the response headers and filter for Cache-Control. In the browser, it's under Response Headers in DevTools → Network.

```sh
curl -sI https://example.com/ | grep -i '^cache-control:'
```

## FAQ

### What's the difference between no-cache and no-store?

no-cache lets caches store the response but makes them check with the server — usually a cheap 304 — before each use. no-store forbids storing it at all.

### What's the longest sensible max-age?

One year (31536000 seconds) is the convention for immutable, fingerprinted assets. Anything that can change at the same URL needs a much shorter lifetime, or no-cache with an ETag.

## Status codes it appears with

- [200 OK](https://www.devkult.com/http/200.md)
- [304 Not Modified](https://www.devkult.com/http/304.md)

## Related headers

- [ETag](https://www.devkult.com/headers/etag.md): An identifier for a specific version of a resource, used to revalidate cached copies and to prevent lost updates.
- [Vary](https://www.devkult.com/headers/vary.md): Lists the request headers that change the response, so caches keep a separate copy for each variant.
- [Last-Modified](https://www.devkult.com/headers/last-modified.md): The date and time the server believes the resource last changed.
- [If-None-Match](https://www.devkult.com/headers/if-none-match.md): Makes a request conditional: send the resource only if its current ETag isn't one of those listed.

## Further reading

- [HTTP caching explained](https://www.devkult.com/blog/http-caching-explained.md)

Open the interactive page: https://www.devkult.com/headers/cache-control