Skip to content
Issue docs

Redirect loop

Importantredirect_loopIssue 166

What is this issue?

A redirect loop is a redirect that leads back to an address already in its own chain, so the request never arrives anywhere:

/pricing  →  301  →  /pricing/
/pricing/ →  301  →  /pricing
/pricing  →  301  →  …

Browsers give up after a fixed number of hops and show "This page isn't working — redirected you too many times". Search engines give up the same way. The page behind the loop cannot be reached by anyone.

For a page to pass this check:

  • No redirect PixyScan follows comes back to an address it has already visited in the same chain, and no page fails to load because its redirects never end.

Example: a rule that adds a trailing slash and another that removes it, both active on the same path. Each undoes the other, forever.

How this differs from "Redirect chains and loops" (#10). #10 now reports chains — redirects that take more than one hop but do arrive. A chain costs time and crawl budget; a loop leaves the page unreachable. A URL that loops is reported here only, never under #10 as well.

Why it matters

  • The page is unreachable. Every visitor who follows a link to it gets a browser error. Whatever the page was for — a product, a sign-up, an article — it is gone for as long as the loop exists.

  • Search engines drop it. Googlebot follows up to ten redirect hops and then reports a redirect error for the URL; the page cannot be indexed, and any ranking it had is lost.

  • Every link to it is wasted. Internal links, sitemap entries and backlinks pointing into the loop pass nothing on, because nothing at the end receives it.

  • It is usually one configuration mistake. Two rules that disagree (http↔https, www↔non-www, slash↔no-slash), a CMS redirect fighting a server redirect, or a cookie check that redirects to a page that redirects back. One fix usually clears every URL affected.

Effect on the health score

This is an important issue under the Link Integrity lens.

How to fix it

  1. Trace the loop. Run curl -sIL --max-redirs 10 https://example.com/pricing and read the location headers. The point where an address repeats is the loop.

  2. Find the two rules that disagree. Almost every loop is two rules undoing each other:

    • http → https at the CDN and https → http at the origin (often a proxy that terminates TLS and an app that thinks it is serving plain HTTP — set X-Forwarded-Proto handling or the app's "behind a proxy" setting);
    • www → non-www in one place and non-www → www in another;
    • "add a trailing slash" and "remove the trailing slash";
    • a CMS redirect and a server redirect on the same path.
  3. Keep one rule and delete the other. Decide the canonical form (https, one host, one slash convention) and make every rule agree with it.

  4. Check login and consent redirects. A page that redirects to /login, which redirects back because a cookie was not set, loops for any client that does not keep cookies — including search engines. Public pages must not depend on a cookie to load.

  5. Re-scan and confirm the page answers 200 (or a single 301 to a page that does).

## Before: both blocks active, each undoing the other
location = /pricing  { return 301 /pricing/; }
location = /pricing/ { return 301 /pricing; }

## After: one canonical form
location = /pricing  { return 301 /pricing/; }

Examples

Example 1: Trailing-slash rules fighting

Fails:

GET /pricing    → 301 Location: /pricing/
GET /pricing/   → 301 Location: /pricing
GET /pricing    → 301 …

Reported here against /pricing, with the recorded chain and hasLoop: true.

Passes: one rule, one direction.

GET /pricing    → 301 Location: /pricing/
GET /pricing/   → 200 OK

Example 2: A loop the crawler could only see as a failure

Fails: the CDN upgrades to https, the origin (behind a TLS-terminating proxy) believes it is on http and redirects to https again — through the CDN, which forwards it as http.

The request never completes. The page is recorded as failed with the reason too_many_redirects and is reported here, with no chain attached.

Example 3: A chain — reported elsewhere

GET /old-pricing → 301 → /pricing → 301 → /pricing/ → 200 OK

Not reported here. The redirects arrive. This is a redirect chain (#10): point /old-pricing straight at /pricing/.

How PixyScan detects this

PixyScan reports a loop from two kinds of evidence.

  1. A recorded redirect chain that repeats an address. When the crawler follows a redirect, it records every hop. If an address appears twice in the chain — or the chain ends on an address it already passed through — the chain is marked as a loop. After the crawl, every such chain is raised here, with the hops it recorded. A chain that loops is raised only here, not also as a redirect chain (#10).

  2. A page that failed to load because its redirects never ended. A true infinite loop never produces a response: the crawler's HTTP client stops after its maximum number of hops and the request fails. PixyScan records why each failed request failed, and a page whose failure was "too many redirects" is raised here too. There is no chain to show in that case, because the client stopped before handing the hops back; the finding says what was observed.

  3. One finding per page. A page found looping by both routes is raised once.

  4. What is not reported here.

    • A redirect chain of two or more hops that does arrive — that is #10.
    • A single redirect with the wrong status code — that is #7.
    • A redirect that arrives at a 4xx page — that is #121.
  5. "Too many redirects" is read as a loop. Strictly it means "more hops than the client allows". No real site needs that many hops, and the page is unreachable either way, so it is reported as the loop it almost always is.

  6. Respects your lens settings. Nothing is raised on a scan with the Link Integrity lens switched off.

What we store

Storage Level

Page Level


Database Table / Prisma Model

RedirectChain (redirect_chains) for loops the crawler recorded hop by hop; Url (urls) for loops that made the page fail to load. The finding is stored on audit_issues.details.


Fields Used — RedirectChain

Field Type Description
redirect_chains.original_url String The address the crawler requested
redirect_chains.final_url String Where the chain ended when it ended
redirect_chains.chain Json Every hop, [{ url, statusCode }]
redirect_chains.chain_length Int Number of hops
redirect_chains.has_loop Boolean True when an address repeats in the chain. The deciding field

Fields Used — Url

Field Type Description
urls.fetch_error_class String Why the page could not be fetched. too_many_redirects raises this check
urls.fetch_error_message String The client's own error text, kept on the finding

Stored Fields on the finding

Field Type Description
message String What was observed
hasLoop Boolean Always true
originalUrl String The looping address
finalUrl String Where the recorded chain stopped (recorded loops only)
chainLength Int Hops recorded (recorded loops only)
chain Json The recorded hops, or null for a loop seen only as a failed fetch
fetchErrorClass String too_many_redirects (failed-fetch loops only)
fetchErrorMessage String The client's error text (failed-fetch loops only)

Detection Dependencies

  • HTTP Response — the redirect hops the crawler followed
  • Crawl failures — the classified reason a request failed

Further reading