SHORT ANSWER
A redirect chain is an old url that redirects to a url that redirects again before a page answers; a loop is a chain that returns to a url it already passed and never answers at all. After a relaunch, chains appear mostly where the previous migration’s rules meet the new map: A → B from last time, B → C now. Google follows up to 10 hops but advises redirecting to the final destination directly. The fix is always the same: rewrite every rule so its source points straight at the final url, and check that the final url answers 200.
Chains, loops, and what Google does with them
chain — /a → /b → /c → 200. The page is reached, one or more hops later than necessary.
loop — /a → /b → /a. The page is never reached; browsers give up with a too-many-redirects error.
Google’s site move guide is direct about chains: “Avoid chaining redirects. While Googlebot can follow up to 10 hops in a ‘chain’ of multiple redirects (for example, Page 1 > Page 2 > Page 3), we advise redirecting to the final destination directly.” The same guide asks to keep chains short — ideally no more than three hops and fewer than five. Its status code documentation adds that the 10 hops are a default for Google’s crawlers and that other products’ crawlers may have different limits.
Loops and over-long chains show up in Search Console’s page indexing report as redirect error, which covers “a redirect chain that was too long”, “a redirect loop”, a redirect url that exceeded the maximum url length, and a bad or empty url in the chain. A chain of two or three hops does not appear there at all — which is why chains survive for years unless you look for them.
Beyond Google: every hop is another round trip for every visitor who arrives through an old link, and other crawlers and link checkers may give up sooner. A one-hop map costs nothing extra to write; a chain is always avoidable.
How chains arise in a relaunch
| cause | what it looks like | where to fix it |
|---|---|---|
| the previous relaunch's rules | /old-2019 → /old-2023 (kept), /old-2023 → /new (new map) | merge both rule sets and flatten |
| protocol and host upgrades | http://example.com/a → https://example.com/a → https://www.example.com/a → /b | one rule that lands on the final https url |
| trailing-slash normalisation | /a → /a/ (platform) → /b (your rule) | write sources in the form the platform serves, or match both |
| targets renamed after mapping | /a → /pricing, then /pricing → /plans added later | rebuild or re-check the map after content changes |
| cms slug redirects | the cms redirects an edited slug, and your map redirects to the old slug | point the map at the current slug |
| a wildcard plus exact rules | /blog/(.*) → /magazine/%1, and /magazine/x → /magazine/y | give the exact row the final target, before the wildcard |
The first cause is the big one, and it is easy to miss on a platform change. When a site moves from one system to another, the old server’s redirect rules do not come along by themselves. Their sources — urls that were already retired once — still receive links and need a rule on the new platform. Re-importing the old file unchanged creates chains wherever its targets are now moving again; dropping it creates 404s. The right move is to merge it into the new map and flatten.
EXAMPLE
the 2019 relaunch redirected /produkte/zange to /shop/zange. The 2026 map moves /shop/zange to /products/pliers. Both rules end up in the new site’s import, and every link to the 2019 url now takes two hops. Flattened, the old rule reads /produkte/zange → /products/pliers.
How loops arise
- two rules pointing at each other.
/a→/bfrom one rule set,/b→/afrom another — typical when a page was moved and later moved back. - a wildcard that matches its own target.
/shop/(.*)→/shop/products/%1: the target starts with/shop/, so it matches the rule again and grows with every hop until the browser gives up. Anchor the pattern or choose a target outside it. - normalisation rules fighting. One layer adds a trailing slash, another removes it; or a cdn forces
wwwwhile the origin forces the bare domain. - a redirect to a page that redirects back. A new page that itself has a redirect to the old url, left over from staging.
Finding chains before and after launch
Three ways, from cheapest to most thorough:
in the spreadsheet — with sources in column A and targets in column B, =COUNTIF(A:A, B2)>0 flags every target that is itself a source. Each flagged row is a chain, and a pair of rows flagging each other is a loop. Works before launch, on the map itself — see the redirect map template.
with curl, against the live site — curl -s -o /dev/null -L -w '%{num_redirects} %{url_effective}' URL prints how many redirects were followed and where the request ended. More than one hop is a chain; curl gives up after its --max-redirs limit (50 by default with -L) on a loop. A loop over a whole url list is in how to check redirects after launch.
with a crawler in list mode — Screaming Frog’s redirect audit tutorial: list mode, the old urls uploaded, “Always Follow Redirects” switched on, then the All Redirects report, which has columns for the number of redirects, whether the chain loops, the final status code and the final address.
Test the http and the non-preferred host variants of a few urls too. A map that is flat for https://www.example.com/a can still take three hops for http://example.com/a.
How to flatten them
- merge every rule set into one table — the new map, the old redirect file, the platform’s existing rules, any server rules. One row per source.
- normalise the spelling of both columns: same host, case and trailing-slash convention. A chain hidden behind
/bvs/b/is still a chain. - follow each target through the table until it reaches a url that is not itself a source. That url is the final target; write it into the row.
- stop on a repeat. If the walk reaches a url it already visited, the rows form a loop: decide which url is the real destination and fix the rule that points back.
- drop identity rows — a source that now points at itself needs no rule, and some importers reject one.
- check the final targets answer 200 on the new site. A flat rule to a 404 is no better than a chain.
For a few dozen rows the spreadsheet formula and a careful afternoon are enough. For more, a short script does the walk. This one reads a two-column csv of paths, prints the flattened rules with the number of hops each one used to take, and reports loops:
import csv, sys
# redirects.csv: header row, then from,to — paths spelled the same way on both sides
rules = {}
with open(sys.argv[1], newline="") as f:
reader = csv.reader(f)
next(reader)
for row in reader:
rules[row[0].strip()] = row[1].strip()
out = csv.writer(sys.stdout)
out.writerow(["from", "to", "hops_before"])
for source in rules:
seen = [source]
target = rules[source]
while target in rules:
if target in seen:
sys.stderr.write("loop: " + " -> ".join(seen + [target]) + "\n")
break
seen.append(target)
target = rules[target]
else:
if target != source:
out.writerow([source, target, len(seen)])Run it as python3 flatten.py redirects.csv > flat.csv. It handles exact rules only; wildcard rows need to be expanded into the urls they cover first, or checked by hand against the exact rows below them.
Where Silentfrog helps, and where it does not
Silentfrog matches old pages only against pages of the new site that answered 200 during the crawl, so the targets it suggests are final at the time of the crawl, not urls that redirect again. A directory rule whose targets do not exist on the new site sends those rows to needs review instead of exporting them silently (whole directories at once).
redirect a whole directory
map every crawled page below a directory at once — optionally as a single webflow wildcard rule that also covers urls the crawl missed.
3 pages · 3 targets exist on the new site · + wildcard /blog/(.*) → /magazin/%1
- /blog/relaunch-checkliste → /magazin/relaunch-checkliste
- /blog/301-vs-302 → /magazin/301-vs-302
- /blog/ladezeit → /magazin/ladezeit
the directory rule form mapping /blog to /magazin with sub-paths kept, exported as a wildcard, and a live preview of how many pages it affects.
What it does not do, so you know where the chain risk still sits:
- an old url that already redirected during the crawl does not get a row of its own — the crawler follows it and maps the page it lands on. The previous relaunch's sources therefore have to be merged and flattened as described above.
- it does not know the redirect rules already stored in your platform or server, so a chain formed by an existing rule plus the new map is caught by the merge step, not by the tool.
- it builds the map; it does not test the live site after launch. That check is a curl loop or a crawler in list mode.
In short
- A chain reaches the page late; a loop never reaches it. Google follows up to 10 hops but wants one.
- Most relaunch chains come from the previous migration's rules meeting the new map.
- Protocol, host and trailing-slash rules add hops of their own — test the http and bare-domain variants too.
- Find chains with =COUNTIF(A:A, B2)>0 before launch and with curl or a list-mode crawl after.
- Flatten: merge all rule sets, follow every target to its end, write the final url, check it answers 200.
For the new half of the map, Silentfrog crawls both sites and matches every old url to a page that answers 200 — the first 50 pages are free.