Skip to main content

URL Redirect Checker

Follow redirect chains step by step and see status codes and headers at each hop.

Follow a URL through its redirect chain and inspect the status code, Location header and final destination at every hop. Which 3xx a server returns is not cosmetic: it decides whether the request method survives, whether the body survives, and whether browsers will still be following that redirect months after you delete the rule.

The five 3xx codes are not interchangeable

RFC 9110 defines them in §15.4. Both 301 (§15.4.2) and 302 (§15.4.3) carry the same note — "For historical reasons, a user agent MAY change the request method from POST to GET for the subsequent request" — and both point at a replacement that forbids it: 308 for the permanent case, 307 for the temporary one. 307 (§15.4.8) states outright that the user agent MUST NOT change the method.

The Fetch standard turns that historical permission into required browser behaviour. On a 301 or 302 following a POST, and on any 303 following a method other than GET or HEAD, the method is set to GET, the body is set to null, and the request-body headers including Content-Type are deleted from the request. A POST redirected through a 302 therefore arrives at the destination as an empty GET, and nothing in the response says so.

303 (§15.4.4) is the code that is supposed to do this. It exists for the POST/redirect/GET pattern, where the outcome of a write is exposed as a separate bookmarkable resource. If you are redirecting after a form submission and want refresh to stop resubmitting, 303 is correct. If you are redirecting an API client and the body has to survive, only 307 and 308 will carry it.

A permanent redirect is close to irreversible

RFC 9110 says a 301 response "is heuristically cacheable", and says the same of 308 — a cache may store and reuse either one with no Cache-Control or Expires header present at all. 302 and 307 carry no such licence, which is the practical difference between the two pairs.

Browsers act on it. A user whose browser has cached a 301 keeps being redirected long after you remove the rule from the server, and they have no way to see why, because the request never leaves their machine. Undoing a mistaken 301 means either waiting out the cache or shipping a counter-redirect from the wrong destination back to the right one, which is its own chain.

The safe migration sequence is to launch on 302 or 307, verify the destination across every path pattern and query shape, and promote to 301 or 308 only once you are confident. When you do promote, send an explicit Cache-Control: max-age with a value you would be willing to live with for that long, rather than leaving the lifetime to a heuristic you do not control.

What the Location header is allowed to contain

RFC 9110 §10.2.2 defines the grammar as Location = URI-reference, so a relative value is entirely legal and is resolved against the target URI using RFC 3986 §5. RFC 2616 originally required an absolute URI, and enough intermediaries and older clients still behave as though that were true that a relative Location: /new can work in a browser and fail in a library against the same server.

The same section specifies fragment inheritance: when the Location value has no fragment, the user agent MUST process the redirect as if it inherits the fragment of the reference that generated the request. Requesting /old#section and receiving Location: /new lands the user on /new#section. If the destination needs a different anchor, or none, you have to send one explicitly.

The Fetch standard adds a constraint servers routinely forget: if the resolved Location has a scheme that is not HTTP or HTTPS, the fetch returns a network error instead of following it. A redirect into a custom application scheme may work for a top-level navigation and will not work through fetch(). When a relative Location resolves somewhere unexpected, the URL parser shows how the reference combines with the base.

Where the chain stops, and what a loop actually looks like

Every client has a hop ceiling and none of them agree. The Fetch standard returns a network error once a request's redirect count reaches 20. curl following redirects with -L stops at --max-redirs, which defaults to 50 and accepts -1 for unlimited. Google's crawler documentation states that its crawlers follow up to 10 redirect hops by default, and that specific products differ — so the same chain can resolve for a user, truncate for a crawler and error in a script.

A loop does not have to be a URL pointing at itself. The common production version alternates between two perfectly reasonable URLs and depends on state: a TLS-terminating proxy forwards to the origin over plain HTTP, the origin sees an insecure request and redirects to HTTPS, and the proxy terminates that again. Each hop is correct in isolation; the browser reports ERR_TOO_MANY_REDIRECTS.

The same shape appears with authentication, locale and consent redirects, where one hop sets a cookie that the next hop does not receive. Because the loop depends on state, it often does not reproduce in a logged-in browser session and only surfaces for a fresh client with an empty cookie jar — which is exactly what a crawler, and this checker, look like.

The hops your credentials do not survive, and the ones you cannot see

The Fetch standard deletes the Authorization header the moment a redirect crosses to a different origin; the specification's own note reads "the moment another origin is seen after the initial request, the Authorization header is removed". curl behaves the same way and needs --location-trusted before it will send credentials to a different host. A chain that returns 401 only at its final hop is usually this, not an expired token.

Cookies follow separate rules, so a chain that resolves cleanly for a signed-in user can break for everyone else. A Secure cookie will not travel over a plain-HTTP hop, a SameSite=Lax cookie is not sent on cross-site subresource requests, and a Domain attribute that does not cover an intermediate host drops the session silently. Trace the chain with no cookies at all before calling it correct.

Two kinds of redirect never appear as a 3xx at all. A <meta http-equiv="refresh"> tag and a JavaScript location.assign() both arrive inside a 200 response with a body, so any HTTP-level tracer stops there and reports success. Chrome also manufactures redirects that never reach the network: an HSTS or HTTPS-upgrade rewrite appears in DevTools as 307 Internal Redirect with a Non-Authoritative-Reason header of HSTS or HttpsUpgrades. That hop is generated inside the browser, so it will never show up in your server logs or a curl trace, and the discrepancy is not a fault in either tool.

ThenCatch is a free developer toolkit. Learn more about us.