A redirect chain is a URL that redirects to a URL that redirects again, so the browser or crawler needs two or more hops before it reaches a page. One redirect is normal; chains appear when rules pile up over the years, and a migration usually adds a whole layer at once. The fix is always the same: point every old address straight at its final URL in one 301, and make your own links use the final URL so they need no redirect at all. This guide shows how chains form, how to find every one (for one URL and for a list of a thousand), and how to flatten them on nginx, Apache and WordPress. It is part of the 301 redirects guide; finding takes minutes, fixing an afternoon on most sites.
Quick answer
- Chains are stacked rules: http → https, then non-www → www, then old slug → new slug, then no slash → slash. Each rule was right on its own.
- Every hop is a full round trip before the page can start loading. Google follows at most 10 hops, and its crawl budget guidance says to avoid long redirect chains because they hurt crawling.
- One URL: the redirect checker lists every hop with its status code. A list: paste the old URLs into the bulk URL checker and sort by hops.
- Fix at the source: every rule points at the final URL, the path change happens inside the scheme and host rule, and internal links, sitemaps and canonicals use final URLs.
- Keep the redirects themselves for at least a year; only your own links should stop needing them.
What is a redirect chain?
Here is a four-hop chain for one old product URL, built from four rules that each made sense:
- Scheme. The https migration sends every
http://request tohttps://on the same host. - Host. The domain change sends
old-site.comtowww.new-site.com. - Path. The CMS migration renamed
/products/red-shoesto/shop/red-shoes. - Slash. The new CMS wants a trailing slash, so
/shop/red-shoesgoes to/shop/red-shoes/.
Nobody notices, because the page eventually appears. A redirect loop is the extreme case, a chain that never ends; ERR_TOO_MANY_REDIRECTS covers those.
Why redirect chains matter
Visitors wait. The browser cannot ask for the page until the last redirect has answered. When a hop changes host, it also needs a new DNS lookup, connection and TLS handshake. Lighthouse measures this as "Avoid multiple page redirects", and the time counts towards Largest Contentful Paint.
Crawlers give up or waste fetches. Googlebot follows up to 10 hops, then reports a redirect error in Search Console. Each hop it follows is a fetch not spent on a page, and Google's crawl budget documentation says plainly: avoid long redirect chains, which have a negative effect on crawling. On a large migrated site, that is weeks of slower recrawling.
Chains hide bugs. A 302 in the middle tells Google the move is temporary, so the old URL keeps ranking (301 vs 302 explains the codes). A hop that drops the query string breaks tracked links.
Google has said that 30x redirects do not lose PageRank, so a single well-formed hop is not a ranking cost. The risk with chains is that one link in them is wrong, temporary or eventually broken, and that crawlers take longer to follow them.
How getReport finds them
The checker requests the URL you enter and follows every redirect, recording the status code and Location of each hop. It then requests the http:// version and the other www variant to see whether those redirect permanently to the final host, and compares the final page's canonical with the URL it landed on.
The finding passes at zero or one hop and warns from two hops up. Each line of the hop list is the status code, the URL that answered and where it sent the browser, so you can see which rule fired at each step.

Lighthouse's view of the same chain: the milliseconds the redirects cost before the page could start.
After a migration, your own links are the biggest source of redirect hits. This info finding lists the links on the page that go through at least one redirect, as "old → final (N hops)"; the section on internal links below shows how to fix them.
How to fix redirect chains
1. Map the chains you have
Run the redirect checker on five addresses: the old home page over http://, the old home page on the other www variant, one old product or post, one old category and one URL with a query string. The hop lists show the order in which your rules fire.
Then check the whole old URL list: the old sitemap (from the Wayback Machine if the old site is gone), top landing pages from analytics before the move, pages with backlinks, and Search Console's "Page with redirect" table. Paste up to 1,000 into the bulk URL checker. Rows with two or more hops are labelled "Redirect chain"; in the CSV, sort by hops and filter redirect_type for temporary or mixed to find stray 302s.
Tip
Group the CSV by final_url. Twenty old URLs that land on the same page through the same hops share one cause, usually one rule, and one fix.
A day of access logs in the log file analyser shows how much of each bot's crawl still goes to redirects.
2. Make the first matching rule know the final destination
Apply the path change inside the scheme and host rules, not after them.
nginx. Keep the path map in one place with map (in the http context, e.g. /etc/nginx/conf.d/redirects.conf) and use it from every server block:
map $uri $new_path {
default "";
~^/products/red-shoes/?$ /shop/red-shoes/;
~^/blog/2021/launch-post/?$ /blog/launch-post/;
}
# http on any host, and https on the old or wrong host: one hop to the final URL
server {
listen 80;
listen 443 ssl;
server_name old-site.com www.old-site.com new-site.com www.new-site.com;
# ssl_certificate lines for all four names
if ($new_path != "") { return 301 https://www.new-site.com$new_path; }
if ($scheme = http) { return 301 https://www.new-site.com$request_uri; }
if ($host != "www.new-site.com") { return 301 https://www.new-site.com$request_uri; }
# ...the final host's own configuration (root, try_files) follows here
}return is one of the few things that are safe inside if. The map matches $uri, which has no query string; $request_uri keeps it for the generic rules. Test with nginx -t, then reload.
Apache. In .htaccess, list the specific moves first with absolute final URLs, then the generic scheme-and-host rule. L stops at the first match, so a moved path never reaches the generic rule:
RewriteEngine On
# 1. Specific moves, straight to the final https URL
RewriteRule ^products/red-shoes/?$ https://www.new-site.com/shop/red-shoes/ [R=301,L]
RewriteRule ^blog/2021/launch-post/?$ https://www.new-site.com/blog/launch-post/ [R=301,L]
# 2. Everything else: https + www in one hop, path and query string kept
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www\.new-site\.com$ [NC]
RewriteRule ^(.*)$ https://www.new-site.com/$1 [R=301,L]If the old domain runs on a different server, its config needs the same specific rules; a bare "redirect the whole domain" rule there makes the host hop come first. Make path rules emit the final trailing-slash form too; trailing slashes, www and https covers that decision.
3. Point old rules at the newest target
When a page moves again, change the existing rule's target (A → C) instead of adding B → C. Export the rules from the server, the CMS plugin or the CDN and look for targets that redirect. The redirect rule generator does this for a two-column list: it points chains straight at their final page and removes loops before writing rules. Change any 302 or 307 on a permanent move to 301.
4. WordPress: plugin rules and core
Most path redirects live in the Redirection plugin or the redirect manager of Rank Math or Yoast SEO Premium. Open each rule whose target is an old address (old domain, http://, or a slug that has since changed) and point it at the final URL. If https and the preferred host are also set at the server or CDN, keep one layer.
WordPress core adds hops of its own: its canonical redirect fixes missing slashes and sends old post slugs (_wp_old_slug) to the new one. It runs after your rules, so a rule that points at a slug without the slash WordPress wants gets one more hop. Point at the exact final form.
Internal links that redirect: fix them at the source
A redirect is a forwarding address for links you do not control. Your own menus, articles and templates can use the final URL: a menu link that redirects costs every visitor a round trip on every click.
The broken link checker requests every link on a page (up to 100 on your host and 50 elsewhere), follows redirects up to 10 hops and keeps each link's final status, final URL and hop count. Read the "Redirects to" column: the Status column shows the final status, so a redirecting link looks healthy at a glance.

Run it on the home page first (its menu links appear on every page), then one page per template. Each pattern has a usual cause:
| Pattern | Usual cause | Where to fix |
|---|---|---|
http:// → https:// | Links typed before the https move, or the site URL setting still on http | Site URL setting, then content |
example.com → www.example.com | Links written with the other host | Content, menus, theme |
/about → /about/ | Mixed trailing-slash style | Menus, content, templates |
/old-slug/ → /new-slug/ | Renamed page, post or category | Menus, content |
Fix the site's own URL first. In WordPress, Settings → General → WordPress Address and Site Address must both be the final form (or WP_HOME and WP_SITEURL in wp-config.php). Otherwise the CMS keeps generating redirecting menu links, canonicals and image URLs.
Then menus and content. Typed custom links in menus never update themselves; menu items picked from a page list do. For content, replace exact old → new pairs with WP-CLI, after a backup and a dry run:
wp db export before-link-fix.sql
wp search-replace 'http://example.com/' 'https://example.com/' --skip-columns=guid --dry-run --report-changed-only
wp search-replace 'http://example.com/' 'https://example.com/' --skip-columns=guid --report-changed-onlywp search-replace handles PHP-serialised data, which a raw SQL REPLACE would corrupt; the Better Search Replace plugin does the same without a command line. Keep full strings with the trailing slash, so http://example.com/ cannot match example.com.au. On Elementor sites, also run Elementor → Tools → Replace URL.
Then everything else that names URLs: the XML sitemap, canonical and hreflang tags, social profiles, ads and email templates.
Platform notes
- Cloudflare and other CDNs. Redirect Rules run before the origin sees the request, so a rule there plus one at the origin is a two-hop chain by design. Either the CDN does scheme and host and the origin does paths (one hop for moved URLs means putting both in the CDN), or the CDN holds the whole map in Bulk Redirects and the origin has none.
- Shopify. URL redirects (Content → Menus → View URL redirects) accept a CSV import. Chains appear when a target is a handle that was renamed later, because Shopify adds its own redirect for the rename.
- Netlify and Vercel. Rules run top to bottom, first match wins: specific paths first, catch-alls last.
Verify
- The redirect checker on your five test URLs reads "The URL loads without a redirect chain" or shows exactly one 301.
- The bulk list, re-run: maximum
hopsis 1,redirect_typeispermanenton every redirected row, and everyfinal_urlanswers 200. The migration redirect validator does the same check against a redirect map, pair by pair. curl -sIL http://old-site.com/products/red-shoes | grep -iE "^(HTTP|location)"prints one301, oneLocation, then200.- The broken link checker on your key pages lists only external links under "Redirects to".
Common mistakes
- Fixing a chain by adding a rule in front. It works until someone reorders the file. Rewrite the existing rules instead.
- Sending the old domain to the new home page. No chains, and every deep link lands on a page Google treats as a soft 404. Redirect mapping is the work that avoids it.
- Forgetting the query string. Tracked links (
?utm_source=) and?page=2must survive.$request_uriin nginx and the default mod_rewrite behaviour keep it. - Removing redirects once your links are fixed. Your links no longer need them; everyone else's do. Keep them for at least a year, as Google's site move guidance says, and for pages with backlinks, for good.
Questions people ask
How many redirects in a chain is too many?
More than one. A single redirect from an old URL to its final page is normal, but every extra hop is another round trip for visitors and another fetch for crawlers. Google stops after 10 hops and reports an error, and its crawl guidance asks you to avoid long chains. Aim for exactly one hop from any old or variant address.
Do redirect chains hurt SEO?
They can. Google says redirects do not lose PageRank, but long chains slow crawling, a temporary hop in the middle keeps the old URL indexed, and a chain over 10 hops is not followed at all. They also slow the page for visitors arriving from old links. Flattening them to one permanent hop removes all of those risks.
Should internal links point to URLs that redirect?
No. Internal links should use the final URL, because every redirecting link costs each visitor a round trip and sends Google through the redirect on every crawl. Update menus, content, templates, canonicals and the sitemap to the final addresses, and keep the redirects themselves for external links, bookmarks and Google's index.
Why do redirect chains appear after a migration?
Because each migration adds rules on top of the old ones instead of replacing them. An https move adds a scheme redirect, a domain change adds a host redirect, and a new CMS adds path and trailing-slash redirects. Each is correct alone, but together an old URL passes through all of them. Rewrite the rules so the first match sends each address to its final URL.