skip to content

You changed a URL that had previously been served with an HTTP 301 Moved Permanently redirect, but returning users still land on the old destination while new users go to the right place. What is happening, and how do you recover?

level: seniorimportance: should knowfreq 38%

answer

  1. 301 cacheable by default, persisted to disk
  2. Server logs clean, incognito works = client cache
  3. No remote invalidation exists
  4. Fix forward from the stale destination
  5. Prevent: explicit max-age; 302 until final

basics

~20 s

Their browsers cached the 301. Because 301 is cacheable by default and browsers persist it, those clients never contact your server for that URL. You cannot invalidate a browser cache remotely — fix it by serving a correcting redirect from the old destination, or by changing the URL so the cache key differs.

solid answer

~60 s

**Diagnosis:** a 301 is cacheable by default and browsers store it on disk, often indefinitely when no `Cache-Control` was sent. Affected clients resolve the redirect locally and never make a request, which is why the server logs look clean and a fresh incognito window works fine. In DevTools the request shows a 301 served "from disk cache" with no network activity. **Recovery** — you cannot reach into their caches: 1. **Fix it at the old destination.** Whatever URL the stale 301 points to must now itself redirect onward to the correct one. That is the only path that reaches already-poisoned clients. 2. **Change the URL** if the destination cannot cooperate — a new path or a version suffix has a different cache key and is unaffected. 3. **Clear the layers you do control** — CDN, reverse proxy, service worker — but expect browser caches to remain. **Prevention:** always send an explicit `Cache-Control: max-age=…` on 301 responses so the lifetime is your choice, and serve 302 or 307 until the mapping is genuinely final.

code

bash · 2 lines
bash
curl -sI https://example.com/old | grep -iE 'HTTP/|location|cache-control|age|x-cache'
curl -sIL --max-redirs 10 -w '%{url_effective} %{num_redirects}\n' -o /dev/null https://example.com/old

go deeper

for a junior

Recognise the cause — the browser cached the permanent redirect and is not asking the server — and know that clearing the cache fixes it for one user.

for a middle

Show the diagnosis: curl the origin, check for Age or X-Cache from an intermediary, confirm with a private window, and explain that 301 is cacheable by default.

for a senior

Own the recovery: purge what you control, then fix forward from the stale destination or change the URL, plus prevention with explicit max-age and staying temporary until the mapping is final.

for a principal

Treat permanent redirects as irrevocable published state: require an explicit lifetime, a promotion process from temporary to permanent, continued control over every destination you have ever pointed at, and monitoring of the resolved chain as a production signal.

## The failure mode `301 Moved Permanently` is one of the statuses a cache may store **without any explicit freshness headers**. Browsers take this further: they persist redirects to disk cache, and with no `Cache-Control` or `Expires` they may keep the entry effectively for the life of that cache. Once a user's browser has stored `old-url → target-A`, the browser resolves the redirect *locally*. Your server is never asked again, so changing the server to say `old-url → target-B` does not reach that user. This is what people mean when they call a 301 a one-way door. ## Confirming it before you fix anything The symptom — some users right, some users wrong — has several possible causes. Separate them: 1. **What does the origin actually say now?** `curl -sI https://example.com/old` bypasses every browser cache. If the `Location` is correct, the server is not the problem. 2. **Is an intermediary caching it?** Check response headers for `Age`, `X-Cache`, `CF-Cache-Status`, `Via`. A CDN-cached redirect is fixable by purging. 3. **Is it the browser?** In DevTools, the request shows status 301 with a size of "(disk cache)" and no network timing. A private window that works confirms it: fresh profile, empty cache. 4. **Is there a service worker?** A registered worker can serve or synthesise redirects independently of the HTTP cache, and it survives cache clears until unregistered. 5. **Are you sure it is not HSTS?** An `http:` to `https:` upgrade is an internal browser redirect reported as 307 and driven by the HSTS policy, not by a server 301. Its own `max-age` governs it, and clearing it is a separate operation. ## Recovering There is no protocol mechanism to invalidate a redirect already stored in a client. Accept that and work with what is reachable. **1. Correct at the stale destination (the only real fix).** If caches point users at `/target-A`, make `/target-A` respond with a redirect to `/target-B`, or simply serve the correct content there. This is why you must always keep the *previous* redirect target under your control — a permanent redirect into a domain or path you later abandon is unrecoverable for cached clients. **2. Change the URL.** Cache entries are keyed by URL. If the old destination cannot be corrected, publish `/new-path-v2`, or add a query parameter, and update links. Existing bookmarks to the poisoned URL remain broken, but every new path is clean. **3. Purge what you own.** Purge the CDN, restart or invalidate the reverse-proxy cache, ship a service-worker update that unregisters or clears its caches. This fixes shared caches and any user whose first exposure was through them. **4. Communicate for the residue.** For a small, known population — an internal tool, a support-driven case — telling users to clear cached images and files, or to visit the correct URL directly once, is a legitimate last resort. It does not scale to the public web. **5. Search engines are a slower version of the same problem.** They also treat 301 as canonical and may keep the mapping for a long time. A corrected 301 chain plus canonical link elements and an updated sitemap re-teaches them, but on a timescale of weeks. ## Preventing the next one **Always set explicit freshness on 301 responses.** `Cache-Control: max-age=86400` (or a week) converts "indefinite, at the browser's discretion" into a bounded window you chose. Some teams deliberately use a short max-age for the first weeks of a migration and lengthen it once stable. **Do not go permanent until the mapping is final.** Serve `302`/`307` during rollout. They are not cacheable by default, so a mistake costs one wrong response rather than a permanent one. Promote to `301`/`308` when the destination has been stable and error-free. **Never point a permanent redirect at something you might not control later.** The destination becomes a long-term commitment: you may need to keep serving a redirect *from* it for years. **Test redirects with fresh state.** Automated checks that follow the chain with a clean client, plus a monitor asserting the final URL and hop count, catch a bad mapping before it is cached by real users. **Instrument the chain.** Log the redirect target and alert on changes; watch for chains growing beyond one hop and for loops. A loop appears the instant a correcting redirect at the old destination happens to point back at the source. ## The summary an interviewer wants "301 is cacheable by default and browsers persist it, so those clients are not talking to my server at all. I confirm with curl and a private window, purge the CDN, and then fix it where it is actually reachable — by redirecting onward from the stale destination or by changing the URL. Going forward I put an explicit max-age on every 301 and stay on 302 until the mapping is final."

  • Can you make a browser forget a cached 301 by serving a 200 at the old URL with Cache-Control: no-store?
    No, because the browser never sends that request — it resolves the stored redirect locally and goes straight to the old target. Headers only take effect on responses the client actually fetches. The only reachable fix is at the destination the stale redirect points to, or moving to a different URL whose cache key was never poisoned.
  • How would you have designed the original migration so this could not happen?
    Serve 302 or 307 during the rollout so nothing becomes sticky, verify the mapping against real traffic and error rates, and only then promote to 301 or 308. Always attach an explicit Cache-Control max-age to permanent redirects so the browser's indefinite default never applies, and choose a destination you are willing to keep controlling for years, since you may need to redirect onward from it later.
  • Users report being stuck on https even though you disabled TLS on a subdomain. Is that a cached 301?
    Probably not. If HSTS was ever sent for that host with includeSubDomains, the browser performs an internal upgrade reported as 307 before any request leaves the machine, governed by the policy's own max-age rather than by the HTTP cache. Recovery means serving a valid HTTPS response with max-age=0 to expire the policy, and if the host is on the preload list, removal takes considerably longer.

You mailed everyone a permanent change-of-address card. Some already updated their address books; you cannot edit those books, so the only way to reach those people is to put a forwarding note at the address you told them about.

saying these in an interview costs you the question

  • Believing you can invalidate a cached 301 by changing the server response for that URL
  • Blaming the CDN without checking whether the request left the browser at all
  • Confusing an HSTS internal 307 upgrade with a server-issued 301
  • Sending permanent redirects with no explicit Cache-Control and calling it fine
  • Telling every public user to clear their cache as the primary remediation

context