---
title: "Cache-Control for SEO: What It Does and How to Set It"
description: "What the Cache-Control header does for SEO: the key directives, how Googlebot uses ETag and Last-Modified, nginx and Apache settings, and how to check."
image: "https://www.blackseoanalyzer.com/static/images/black-seo-analyzer-og-image.png"
canonical: "https://www.blackseoanalyzer.com/en/blog/cache-control-for-seo"
language: "en"
---

# Cache-Control for SEO: What the Header Does and How to Set It

By [Seth Black](https://www.blackseoanalyzer.com/en/about) · Published November 01, 2026

Cache-Control is the HTTP response header that tells browsers, CDNs and other caches whether they may store a response and for how long. It isn’t a ranking factor on its own. It matters for SEO in two indirect ways: cached pages and assets load faster for real users, and a server that answers revalidation requests cheaply lets crawlers spend their requests on pages that changed.

## What does Cache-Control do?

A response carries one header with one or more directives:

```http
HTTP/1.1 200 OK
Content-Type: text/css
Cache-Control: public, max-age=31536000, immutable
```

These are the directives you’ll actually use. Definitions follow [MDN’s Cache-Control reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control):

| Directive | What it means |
| --- | --- |
| `max-age=N` | The response is fresh for N seconds. |
| `s-maxage=N` | Same, but only for shared caches such as a CDN. Overrides `max-age` there. |
| `no-cache` | May be stored, but must be revalidated with the origin before each reuse. |
| `no-store` | Must not be stored by any cache. |
| `private` | Only the browser may store it, not a CDN. |
| `public` | Shared caches may store it. |
| `must-revalidate` | Once stale, must be revalidated before reuse. |
| `immutable` | Won’t change while fresh, so don’t revalidate even on reload. |
| `stale-while-revalidate=N` | After it goes stale, a cache may serve it for N more seconds while refreshing in the background. |

The one people get backwards: `no-cache` doesn’t mean “don’t cache”. It means “check before you reuse”. If you mean “never store this”, that’s `no-store`.

## Does Cache-Control affect SEO?

Not directly. There’s no ranking signal for the header. What it changes:

**Speed for real users.** Core Web Vitals come from real visits, and repeat visits with cached CSS, JavaScript, fonts and images render faster. HTML cached at a CDN edge cuts time to first byte for everyone, which feeds Largest Contentful Paint. For more on reading those numbers per page, see [how to diagnose Core Web Vitals failures at the page level](https://www.blackseoanalyzer.com/en/blog/how-to-diagnose-core-web-vitals-failures-at-the-page-level).

**Crawl efficiency.** Google’s crawlers support conditional requests. Google’s [crawler documentation](https://developers.google.com/crawling/docs/crawlers-fetchers/overview-google-crawlers) says its crawling infrastructure supports `ETag` with `If-None-Match` and `Last-Modified` with `If-Modified-Since`, prefers `ETag` when both are present, and that other HTTP caching directives aren’t supported. Setting `max-age` alongside `Last-Modified` is described as optional and as something that can help crawlers decide when to recrawl. In practice: send a stable `ETag`, answer `304 Not Modified` when nothing changed, and Googlebot gets a cheap response instead of a full page.

**robots.txt.** Google’s [robots.txt specification](https://developers.google.com/crawling/docs/robots-txt/robots-txt-spec) says Google generally caches robots.txt for up to 24 hours and may adjust that based on `max-age`. A robots.txt change can take a day to be seen, and a long `max-age` on it is a way to make that worse.

## What should Cache-Control be for each kind of file?

| Resource | Suggested header | Why |
| --- | --- | --- |
| Fingerprinted assets (`app.3f9a1c.js`) | `public, max-age=31536000, immutable` | The URL changes when the file changes, so cache it for a year. |
| Assets without a fingerprint | `public, max-age=86400` or less | Nothing tells caches when the file changed. |
| Public HTML pages | `public, max-age=0, s-maxage=300, stale-while-revalidate=60` | Browsers always revalidate; the CDN holds it briefly. |
| Logged-in or personal pages | `private, no-store` | Never let a CDN serve one user’s page to another. |
| robots.txt and sitemaps | `public, max-age=3600` | Short enough that changes land quickly. |

The rule underneath all of it: long cache lifetimes only on URLs that change when the content changes. That’s what fingerprinting (a content hash in the file name) is for.

## How to set Cache-Control in nginx

```nginx
# Fingerprinted static assets: one year, never revalidate
location ~* \.(?:css|js|woff2|png|jpg|jpeg|webp|avif|svg)$ {
    add_header Cache-Control "public, max-age=31536000, immutable" always;
}

# HTML: browsers revalidate, CDN keeps it five minutes
location / {
    add_header Cache-Control "public, max-age=0, s-maxage=300, stale-while-revalidate=60" always;
}
```

nginx sends `ETag` for static files by default. The same `add_header` inheritance trap I described in the [X-Robots-Tag post](https://www.blackseoanalyzer.com/en/blog/x-robots-tag) applies here: a `location` with its own `add_header` drops every `add_header` from the `server` block.

## How to set Cache-Control in Apache

```apache
<IfModule mod_headers.c>
  <FilesMatch "\.(css|js|woff2|png|jpe?g|webp|avif|svg)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>
  <FilesMatch "\.html$">
    Header set Cache-Control "public, max-age=0, s-maxage=300"
  </FilesMatch>
</IfModule>
```

Only use the one-year rule if your build fingerprints file names. If it doesn’t, a year-long cache means visitors keep last month’s CSS.

## How do you check a page’s Cache-Control?

```bash
curl -s -D - -o /dev/null https://example.com/ | grep -iE 'cache-control|etag|last-modified|age:'
```

Then test revalidation by sending the `ETag` back:

```bash
curl -s -o /dev/null -w '%{http_code}\n' \
  -H 'If-None-Match: "abc123"' https://example.com/
```

A `304` means conditional requests work. A `200` with the same `ETag` means the server ignores them and sends the full page every time. Behind a CDN, also check the CDN’s own status header (`cf-cache-status`, `x-cache` and similar) to see whether the edge served it.

## Common Cache-Control mistakes

**Caching HTML for a long time.** A page with `max-age=86400` keeps serving yesterday’s version, including yesterday’s `noindex` or broken canonical, until it expires. Keep HTML short-lived and purge the CDN on deploy.

**Caching errors and redirects.** A CDN that caches a temporary 404 or a 302 for hours turns a blip into an outage for crawlers. Give error responses a short lifetime or none.

**`no-store` everywhere.** Some frameworks send it on every response by default. That throws away all caching for public pages and forces full downloads for users and crawlers alike.

**Year-long caching without fingerprints.** The asset never updates for returning visitors.

## What bseoa checks, and what it doesn’t

Honest limitation first: bseoa’s page analysis doesn’t fetch every subresource’s headers, so it isn’t the tool for auditing Cache-Control on each CSS or image file. Use `curl` or the browser’s Network panel for that, and read our reference pages on a [missing Cache-Control header](https://www.blackseoanalyzer.com/en/technical-seo/performance-analyzer/missing-cache-control-header) and [insufficient cache time](https://www.blackseoanalyzer.com/en/technical-seo/performance-analyzer/insufficient-cache-time) for the fixes. What a crawl does give you is time to first byte for every page: [bseoa’s website crawler](https://www.blackseoanalyzer.com/en/website-crawler) records `ttfb_millis` per URL in its `jsonl-summary` output, so sorting by it shows which pages your CDN isn’t serving from cache.

-Sethers

About the author

[Seth Black](https://www.blackseoanalyzer.com/en/about)

Seth is a software engineer and engineering leader who builds bseoa, a Rust-based technical SEO crawler with a GUI and CLI. He writes about the crawling, rendering and indexing problems he runs into on real sites.

[LinkedIn](https://www.linkedin.com/in/seth-black-tx/) · [GitHub](https://github.com/sethblack) · [YouTube](https://www.youtube.com/@SethBlack)

[Back to Blog](https://www.blackseoanalyzer.com/en/blog)

## Discover hundreds of SEO Issues in Seconds

Without Monthly Subscriptions

Comprehensive technical SEO analysis powered by ML and 16 specialized modules. Optional AI-powered insights from Claude, GPT-4, or Gemini. Get actionable insights in seconds, and never pay monthly fees again.

[Download Free Trial](https://www.blackseoanalyzer.com/en/free-trial)

I use AI to generate images for my posts and for general editing, updates, and ironically SEO purposes. I used to draw all of the images for my personal blog (taleas) myself, but as the volume of content I produce has increased, I've turned to AI tools to help create visuals that complement my writing. I go out of my way to generate images that look strange, and don't represent real people. If you ever want to chat about my use of AI, please reach out.

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://www.blackseoanalyzer.com/#organization","name":"Fiscus Technology, LLC","url":"https://www.blackseoanalyzer.com/","logo":{"@type":"ImageObject","url":"https://www.blackseoanalyzer.com/static/images/black-seo-analyzer.png"},"sameAs":["https://sethserver.com/","https://github.com/sethblack"],"founder":{"@id":"https://www.blackseoanalyzer.com/#seth"}},{"@type":"WebSite","@id":"https://www.blackseoanalyzer.com/#website","name":"bseoa","url":"https://www.blackseoanalyzer.com/","publisher":{"@id":"https://www.blackseoanalyzer.com/#organization"}},{"@type":"Person","@id":"https://www.blackseoanalyzer.com/#seth","name":"Seth Black","url":"https://www.blackseoanalyzer.com/about","jobTitle":"Creator of bseoa","sameAs":["https://www.linkedin.com/in/seth-black-tx/","https://github.com/sethblack","https://www.youtube.com/@SethBlack","https://sethserver.com/"]},{"@type":"BlogPosting","@id":"https://www.blackseoanalyzer.com/en/blog/cache-control-for-seo#article","headline":"Cache-Control for SEO: What the Header Does and How to Set It","name":"Cache-Control for SEO: What the Header Does and How to Set It","description":"What the Cache-Control header does for SEO: the key directives, how Googlebot uses ETag and Last-Modified, nginx and Apache settings, and how to check.","url":"https://www.blackseoanalyzer.com/en/blog/cache-control-for-seo","datePublished":"2026-11-01T05:21:24","dateModified":"2026-11-01T05:21:24","author":{"@id":"https://www.blackseoanalyzer.com/#seth"},"publisher":{"@id":"https://www.blackseoanalyzer.com/#organization"},"image":{"@type":"ImageObject","url":"https://www.blackseoanalyzer.com/static/images/black-seo-analyzer-og-image.png","width":1200,"height":630},"mainEntityOfPage":{"@type":"WebPage","@id":"https://www.blackseoanalyzer.com/en/blog/cache-control-for-seo"},"isPartOf":{"@id":"https://www.blackseoanalyzer.com/#website"},"inLanguage":"en-US","isAccessibleForFree":true,"wordCount":1033},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://www.blackseoanalyzer.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://www.blackseoanalyzer.com/en/blog"},{"@type":"ListItem","position":3,"name":"Cache-Control for SEO: What the Header Does and How to Set It","item":"https://www.blackseoanalyzer.com/en/blog/cache-control-for-seo"}]}]}
```
