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.
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; overridesmax-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.