A redirect that works in your browser can still fail every crawler

Sources: RFC 9110. First-hand header checks against this site's own production redirects, measured 2026-09-01.

The short version

  • A redirect can work perfectly in every browser and still ship a header a crawler reads differently, because a browser resolves and caches details a crawler only ever receives raw.
  • brandonlazovic.dev's own production redirect for a renamed tool page returns a relative Location header, which RFC 9110 permits, and no Cache-Control directive at all, which the standard does not require but also does not protect against.
  • This site's own publish gate stayed green while a real visual assertion was quietly checking nothing, because the rule it was supposed to guard was also being set by an unrelated shorthand property.
  • Reading your own redirect headers with curl -I takes under five minutes and finds exactly the class of defect a gate that only reads text and markup cannot see.
Watch: A redirect that works in Chrome can still fail a crawler

A redirect is a server’s instruction telling a visitor to fetch a different URL instead of the one it asked for, and a browser carries out that instruction so smoothly that most people never notice it happened. A crawler, the automated program a search engine sends out to fetch and read pages, gets the same instruction and reads it far less generously. It sees only a status code, the three-digit number a server returns to say what happened, plus a short stack of headers, the lines of metadata that ride along with every response. brandonlazovic.dev’s own production redirect for a renamed tool page returns a 301, the status code for a permanent redirect. That response is missing a caching instruction entirely, the realer of the two findings here. It also points to its destination with a bare path instead of a full address, a risk the standard itself permits. Neither shows up in anything you can see in a browser, and a crawler cannot quietly fix either one for you.

brandonlazovic.dev's own production 301 redirect ships a relative Location header and no Cache-Control directive at all.

What does a browser hide when it follows a redirect?

A browser hides three separate judgment calls behind the one page it finally shows you: resolving the redirect’s destination, picking a caching rule, and loading the new page. When a server answers with a redirect, a response carrying a 3xx status code, the browser reads the destination from the Location header, the response field naming where to go next, and resolves it into a full address even when the server sent only a path.

You experience one click and one page load. The browser made at least three separate judgment calls to get you there, and it made all three silently.

Open a browser’s developer tools and watch the Network tab during a redirect, and you can see the raw exchange the address bar hides: the original request, the 3xx response, the second request the browser fired on its own. That view is closer to what a crawler actually receives. A crawler does not have a rendering engine standing between it and the response, forgiving small omissions before you ever see them. It gets the bytes.

What are the three ways a working redirect still fails a crawler?

A redirect can point to the correct destination, work for every human tester who tries it by hand, and still fail a crawler in three distinct ways: a header written in a format some clients do not resolve, no instruction for how long the answer should be trusted, and a permanence signal that does not match what actually happened to the page.

Each failure hides in a browser because a browser is built to paper over exactly the reading a crawler cannot skip. The first two are not hypothetical on this site. They are both live on brandonlazovic.dev’s own redirect for a renamed tool, confirmed with curl, a command-line tool that fetches a page’s raw HTTP response headers, on 2026-09-01.2 The third, a temporary status code left in place on a change that will never revert, is a real risk described by the standard rather than a defect measured here. All three matter for the same reason: a redirect’s entire job is telling a crawler which URL is now canonical, the version a search engine should treat as the authoritative one among near-duplicates, and each of these three failures muddies that one signal in a different way.

What does a relative Location header actually break?

A relative Location header breaks nothing for a modern crawler or a modern browser. RFC 9110, the current HTTP standard, permits a client to use the “Location header field value after resolving it relative to the original request’s target URI.”1 That sentence allows a server to send a path instead of a full address, and expects the receiving client to fill in the rest itself.

This site’s own redirect does exactly that: a request to its old tool URL returns a 301 whose Location header reads /tools/ai-watermark-detection-rate-calculator/, a path with no scheme or hostname attached, confirmed by curl on 2026-09-01.2 The real exposure here is historical, not present-tense.

An older HTTP standard, RFC 2616, once required an absolute URL in that header, and RFC 7231 later relaxed the rule to what RFC 9110 states today. A client built strictly against that older requirement can fail on a path-only value even though nothing here violates the current spec. Calling this a broken header would be wrong, and it would fail exactly the kind of check that exists to catch an inaccurate claim. The honest label is a legacy-client risk: real for whatever share of crawlers and tools still assume the old rule, and irrelevant to everything built against the current one.

Why does a missing Cache-Control matter on a 301?

A missing Cache-Control header matters on a 301 because the response still gets cached. It just gets cached on a duration nobody chose. RFC 9110 states that a 301 response “can be reused by a cache with heuristic expiration unless otherwise indicated by the method definition or explicit cache controls.”1

Silence, in other words, is not neutral. It hands the caching decision to whichever cache handles the request next, with no guarantee that its guess matches your intent.

This site’s own redirect carries no Cache-Control directive anywhere in the response. The full header list that comes back is Date, Connection, Location, content-signal, Speculation-Rules, Report-To, Nel, Server, CF-RAY and alt-svc, confirmed by curl on 2026-09-01.2 Among those ten headers, none says how long anything downstream should trust this answer. A missing Cache-Control instruction is a bit like leaving a note for a substitute teacher with no date written on it, and whoever finds the note decides for themselves how long it still applies. Unlike a confused substitute, a cache runs an actual procedure called heuristic expiration, a scheme RFC 9110 permits without fully specifying, so the guess follows a real, repeatable rule. The practical effect is the same either way. How long a crawler’s cache holds onto this redirect was never anybody’s decision.

What does this site’s own redirect chain return?

This site’s own redirect chain returns a working page on every hop, confirmed by curl on 2026-09-01.2 A plain HTTP request to the bare domain returns a 301 to HTTPS. Requests to /about, /writing, /tools and /editorial-standards each return a 307, a temporary redirect, to the identical path with a trailing slash added, and that 307 then resolves to a 200. The renamed tool page returns a 301 whose Location header points at /tools/ai-watermark-detection-rate-calculator/.

Request Result
http://brandonlazovic.dev/ 301 to https://brandonlazovic.dev/
https://brandonlazovic.dev/about 307 to /about/, then 200
/writing, /tools, /editorial-standards Same pattern, four for four
/tools/provenance-recall-calculator/ 301, Location: /tools/ai-watermark-detection-rate-calculator/
/tools/ai-watermark-detection-rate-calculator/ 200, confirmed by curl on 2026-09-01

One honest aside belongs here. The first pass at this check probed https://brandonlazovic.dev/articles and read its 404 response as a broken trailing-slash redirect. There is no index page for that route, so a 404 is the correct answer and nothing was broken. Testing the wrong URL and drawing a conclusion from it is precisely the failure mode this whole piece is warning about, and it happened while writing it.

Why does one text-based check miss all of this?

npm run audit, this site’s own gate for publishing anything, misses the relative Location header and the missing Cache-Control directive because it reads text and markup, never a live HTTP response. It runs four scripts in sequence: a lockfile check, a check on the published skills index, an SEO markup audit and a citation check, and every one of them operates on files on disk before a browser ever loads a page.

None of the four issues a request, so none of them can see a Location header, a missing Cache-Control directive, or which status code actually came back. A separate command, npm run shoot, closes part of that gap by loading the built pages in a real browser and reading computed styles off the rendered page, catching a class of defect the text-only audit is structurally blind to. But it reads what a page looks like once rendered, not the raw headers a server returned to get it there. Redirect headers sit outside what either gate inspects, which is exactly how a relative Location header and a missing Cache-Control directive shipped to production unnoticed. A green result from either script answers a narrower question than “is this correct.”

When is a green check actually checking nothing?

A green check can be checking nothing at all when the specific behavior it claims to guard is also produced by some other rule, so breaking the first rule on purpose leaves the check’s answer unchanged. That happened on this site’s own visual gate, first-hand and dated 2026-09-01. Building a style assertion for a different article’s component, the first check written asserted that a script-created row’s border-left-width was not 0px.

Calibrating that assertion means deliberately breaking the rule it exists to guard and confirming the check then fails. It did not. The same CSS rule also set border: 1px solid as shorthand, and that shorthand sets border-left-width on its own. Deleting the intended border-left: 3px rule only moved the computed value from 3px down to 1px, and the assertion, which only checked for “not zero,” kept passing.3 npm run audit stayed green through every version of this, because the markup was structurally correct the whole time and only the pixels were wrong. Swapping the assertion to border-radius, a property nothing else in the rule touches, fixed it: the calibration run then failed exactly when it should have.

Call a check like the first one a vacuous assertion. It passes because it reads a value some unrelated rule already supplies, not because it verified the thing it names. The only way to find one is to break the rule on purpose and watch whether the check actually notices. A check nobody has ever tried to fail is a check nobody has verified.

How do you read your own redirect headers in five minutes?

You read your own redirect headers by running curl -I against the URL. Run curl -I https://yoursite.com/some-redirecting-path and look at three things: whether the Location line is a full address or a bare path, whether any Cache-Control line appears at all, and whether the status code is a 301 for something permanent or a 302 or 307 for something that will genuinely revert later.

A request like that returns only the response headers, with no page body attached, exactly the raw view a crawler gets and a browser normally hides.

If a terminal is not where you want to do this, this article ships a pasted-header analyzer above that runs the same checks in a browser. It cannot fetch a URL for you and read the response itself, because a browser’s same-origin security policy blocks a page from reading another site’s response headers directly, so you still run curl -I yourself and paste the output in. It flags a relative Location as a legacy-client risk (the same label this piece uses throughout), flags a 302 or 307 sitting on a URL that will never revert, flags a Location pointing back at a URL already in the pasted chain, and flags a missing or very short Cache-Control. It comes seeded with this site’s own real 301, so you can see what a flagged response looks like before you paste your own.

None of this replaces the two checks a build pipeline already runs. It replaces the assumption that passing them means nothing else needs a look. The robots.txt precedence piece checked a rule against 19 live files instead of trusting a summary, and the log-collection piece built a second, independent count just to confirm the first one was real. A redirect that works for you is a claim about your own browser. Reading the headers is the only way to find out if it also works for everything else reading them.

Paste your own headers

This tool cannot fetch a URL for you. A browser's same-origin policy blocks a page from reading another site's response headers directly, even your own, so there is no "paste a URL" version of this that could actually work from inside an article. Run curl -I yourself, or curl -I -L to follow a whole chain, and paste what it prints below. It is pre-loaded with this site's own redirect so you can see what it catches before pasting anything.

Paste the response headers

Run curl -I -L https://example.com/old-url and it prints one blank-line-separated block per hop. Paste all of it and each hop gets checked on its own, including whether a later hop's Location points back at a URL that already showed up earlier in the paste.

Date, the Report-To endpoint token, and CF-RAY above are representative filler; those three vary on every real request. The status, the Location path, the full set of header names, and the missing Cache-Control are measured against production, 2026-09-01.

What this catches

Terms defined here

  • Vacuous assertion. A check that passes because it reads a value some other, unrelated rule already supplies, so breaking the specific behavior the check claims to guard does not change its result. The idea sits close to established mutation-testing language, where a test that survives a deliberate mutation is the same failure mode wearing a different name.

Sources

  1. RFC 9110: HTTP Semantics, Location and 3xx status codes
  2. First-hand: header checks against brandonlazovic.dev's own production redirects, measured 2026-09-01
  3. First-hand: audit-versus-shoot calibration on this site's publish gate, measured 2026-09-01