Dev.to WebDev 🛠 Dev 👁 0 📖 5 min read

You fixed the link, but it's still broken: how cached 404s and redirects outlive your fix

You get a report that a link is broken. You find the cause, restore the page or add a redirect, test it yourself and it works. An hour later, another report: still broken. You're not imagining it, and they're not either

You get a report that a link is broken. You find the cause, restore the page or add a redirect, test it yourself and it works. An hour later, another report: still broken.

You're not imagining it, and they're not either. HTTP responses get cached in several places between your server and the reader, and errors can be cached just like successful pages. So can redirects, sometimes for much longer than you'd expect.

This post walks through each layer that can remember a broken response, why it does, and how to work out which one is doing it.

Layer 1: the browser cache

Browsers cache responses according to HTTP caching rules (defined in RFC 9111). The headers that matter most are Cache-Control and, for older setups, Expires.

A 404 response is "cacheable by default" under those rules, meaning a cache may store it and reuse it using heuristics even if there are no explicit caching headers. In practice, browsers tend to be conservative with error responses, but if your server sends something like Cache-Control: public, max-age=86400 on every response, including 404s (a common result of a blanket config rule), the browser can legitimately keep showing the 404 for a day.

How to tell: ask the reader to try a private window, or test with curl, which has no cache. If curl sees the fixed page and the browser doesn't, it's local.

Layer 2: cached redirects (the long-lived one)

Permanent redirects are the most surprising case. A 301 or 308 is cacheable by default, and browsers often cache them aggressively. If you once had a mistaken 301 from /pricing to /plans and later remove it, a browser that saw the 301 may keep jumping straight to /plans without ever asking your server about /pricing again, potentially for a long time.

That's why "temporarily" using a 301 during a migration can be painful to undo. If you're unsure whether a redirect is permanent, a 302 or 307 is easier to take back.

How to tell: open the browser's network panel with "Disable cache" ticked and compare. A redirect served "from disk cache" or "from memory cache" is the giveaway.

Layer 3: the CDN or reverse proxy

If your site sits behind a CDN or a caching proxy (Varnish, nginx proxy_cache, a platform's edge network), that layer may have cached the 404. Many CDNs have a separate setting for how long to cache error responses, sometimes called "negative caching" or error caching, and the default varies between providers and plans. Some cache 404s for a short time by default to protect your origin from floods of requests for missing URLs.

This is the most common cause of "fixed but still broken for everyone": your origin is now correct, but the edge keeps serving the stored error until its TTL expires.

How to tell: look at the response headers. Most CDNs add headers that reveal cache status, often something like Age, X-Cache: HIT or a provider-specific header. An Age value larger than the time since your fix is proof you're seeing an old copy.

curl -sI https://example.com/docs/install | grep -iE '^(HTTP|age|cache-control|x-cache|cf-cache-status)'

How to fix: purge that URL from the CDN, and review your error caching settings.

Layer 4: the application's own cache

Frameworks and CMSs often have their own page caches: static HTML generated at build time, a full-page cache plugin, an in-memory route cache. If the 404 was generated and cached at that level, fixing the content in the database won't change what's served until the cache is rebuilt.

Static site generators are a special case: if the page was missing at build time, it stays missing until the next build and deploy, however many times you fix the source.

How to tell: request the origin directly (bypassing the CDN, for example via an internal hostname or curl --resolve) and see whether the origin itself still returns 404.

Layer 5: DNS (when the link points at a hostname that changed)

If the broken link was caused by a hostname problem (a missing DNS record for docs.example.com, say) then DNS caching applies. Resolvers cache answers for the record's TTL, and many also cache "this name doesn't exist" responses for a period derived from the zone's settings. A newly added record may not be visible to everyone until those cached negative answers expire.

How to tell: query a few public resolvers directly with dig and compare.

Layer 6: search engines and link previews

Finally, things outside your control. A search engine that crawled the broken URL may show it as an error or drop it from results until it's recrawled. Chat apps and social platforms cache link previews, sometimes for a long time, so a shared link may keep showing an error card even after the page is back. Many platforms offer a tool to refresh a preview.

A decision tree for "still broken"

  1. Does curl against the public URL return the fixed response?
    • No → the problem is server-side: CDN, app cache or origin. Check Age and cache headers, then the origin directly.
    • Yes → the problem is client-side or downstream: browser cache, cached redirect, DNS on their network, or a cached preview.
  2. Is it a redirect? Suspect browser-cached 301s first.
  3. Is it a hostname? Suspect negative DNS caching.

Prevention

  • Don't send long max-age on error responses. Set short or no caching for 4xx and 5xx explicitly.
  • Use temporary redirects until you're sure. Switch to 301 or 308 once a redirect is definitely permanent.
  • Know your CDN's error caching settings and how to purge a single URL quickly.
  • Check links against the public URL, the way readers see it, not just against your origin or source files.

That last habit is what linkbruno.com does: it checks the links on your pages from the outside, through all the same caches your readers go through.

Related reading

Which layer has kept a broken link alive the longest for you: the browser, the CDN, or something stranger?

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.