scalpel@labs: ~/glossary/cache-control-header.mdx5 sections

Cache-Control Header

Cache-Control is the response header that tells browsers and shared caches whether and for how long they may reuse a response without asking the server again. On a redirect, it governs how long the redirect decision itself is replayed locally.

extension: Scalpel Redirectsupdated: 2026-08-14read_time: 3 min
less cache-control-header.mdx

Why it matters

Redirect caching is where mistakes stick. A 301 is cacheable by default; many browsers treat an unqualified one as good indefinitely. After you fix a bad 301 on the server, users who already hold it in cache keep seeing the old redirect. No server-side correction reaches them. Explicit Cache-Control on redirects is how you keep an undo button.

Test with a 302 first (not cached by default), verify it works, then harden it to a 301 with a deliberate max-age value. This avoids the stuck-redirect scenario entirely.

How it works

The Cache-Control header is a list of directives. The ones that matter for redirects are:

  • max-age=N: The response is fresh for N seconds; after that, revalidate with the server.
  • no-store: Do not cache this response at all.
  • no-cache: Cache it, but always revalidate before using it.
  • public / private: Whether a shared cache (CDN) may store it.
  • s-maxage=N: For CDNs only; overrides max-age.

Status code defaults matter. A 301 or 308 is cacheable by default (browsers may cache it for a very long time). A 302 or 307 is not cached by default unless the response includes Cache-Control: max-age=....

When a 301 is cached locally, every subsequent visit to that URL replays the cached redirect without touching the network, even if the server has changed it. An incognito/private window bypasses browser cache, so if your fix works in incognito but not in a normal tab, a cached redirect is the culprit.

What does not matter

A Cache-Control header on a 301 to a URL that itself redirects does not carry through. Caching applies to each hop independently. If hop 1 is a 301 with max-age=31536000, and hop 2 is a 302, the 302 is not cached by default.

The HTTP specification does not mandate how long a browser keeps a permanent redirect in cache. Some browsers honor max-age; others have their own indefinite caching. The practical safeguard is: never rely on Cache-Control alone to undo a mistake. Test with a 302 first.

Code example

A 301 with explicit cache time:

HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page
Cache-Control: max-age=3600, public

The same URL with a 302 (safer for testing):

HTTP/1.1 302 Found
Location: https://example.com/new-page

(A 302 has no explicit Cache-Control, so browsers do not cache it; every visit re-checks the server.)

A stuck-redirect incident: after shipping a bad 301, you fix the server. But users still see the old redirect because their browsers are replaying it from cache. Clear it with a short cache header sent early:

HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page
Cache-Control: max-age=0, no-cache, no-store, must-revalidate

Send this for a few hours before removing the 301 entirely. This tells browsers "do not cache this at all" and forces them to contact the server on every visit.

nginx example:

location /old-url {
  return 301 https://example.com/new-page;
  add_header Cache-Control "max-age=3600, public" always;
}

How Scalpel shows it

Scalpel Redirects surfaces the Cache-Control header for every hop. A hop with a long max-age on a 301 gets a visual hint so you can see an aggressively cached redirect before it becomes an incident. If the hop is served from cache, both the Cache-Control value and the from-cache flag appear together.

Sources