Skip to content
SEO

Redirect Chains: How to Find and Flatten Them on NGINX

Redirect Chains: How to Find and Flatten Them on NGINX
In this article
  1. What Are Redirect Chains and Why Do They Build Up After Site Moves?
  2. How Does Google Handle Multiple Redirects?
  3. How to Check Redirects with curl
  4. Flattening Redirect Chains with NGINX return Rules
  5. Testing and Maintaining the Flattened Rules
  6. FAQ

Redirect chains happen when an old URL goes through several redirects before it reaches the final page. For example: http, then www, then a trailing slash, then an old path rule. The fix is to point every legacy URL straight at its final HTTPS address with a single NGINX return rule. How do they pile up? Usually over several site moves, because each move stacked its own rule on top of the rules left by the move before.

What Are Redirect Chains and Why Do They Build Up After Site Moves?

A redirect chain is a series of redirects where each hop points to another redirect instead of the final page. After a few migrations, a typical one looks like this:

http://example.com/old → https://example.com/old → https://www.example.com/old → https://www.example.com/old/ → https://www.example.com/new/

Four hops. For one page. Each move leaves its rules in a separate server block or location, and they fire one after another. A chain still ends at a real page, and that’s what separates it from a loop, where the URL never resolves at all. Got a proxy in front of your origin? Then rule out the pattern behind WordPress redirect loops behind proxies first.

How Does Google Handle Multiple Redirects?

Googlebot follows redirects, and permanent server-side redirects tell Google to treat the target as canonical. According to Google guidance on redirects, server-side redirects are the method Google is most likely to read correctly. So NGINX rules beat meta refresh or JavaScript. Google’s page doesn’t give a hop limit. But think about it: every extra hop is one more request for visitors and crawlers alike, and it buys you nothing. Choosing the status code is a separate question (we cover it in the guide to permanent versus temporary redirects).

How to Check Redirects with curl

curl -I shows the status code and Location header for one hop, without downloading the page. Add -L to follow the whole path, then filter the output down to just the hops:

curl -sIL http://example.com/old | grep -Ei '^(HTTP|location)'

In practice, I’d run the audit like this:

  1. Collect old URLs from past sitemaps, backlink reports and earlier redirect maps.
  2. Test each path in all four variants: http and https, with and without www.
  3. Record the number of hops and the final URL for each variant.

A short loop flags every URL that returns more than one Location header:

while read -r url; do
  n=$(curl -sIL "$url" | grep -ci '^location:')
  [ "$n" -gt 1 ] && echo "$n hops: $url"
done < urls.txt

Loops look different. curl keeps following redirects until it hits its --max-redirs limit, then bails out with an error. No final 200.

Flattening Redirect Chains with NGINX return Rules

The idea is simple. Build the full target URL (right scheme, right host, right path) inside one return 301, so every non-canonical request lands in a single hop. One catch-all block sending https://www.example.com$request_uri replaces the separate http-to-https and www rules. Legacy paths go into a map that gets checked before the host redirect, which means an old path jumps straight to the final page. And write every target with its trailing slash. Otherwise a slash rule fires afterwards and you’re back to two hops. For plain one-to-one moves, return is clearer and cheaper than rewrite, no contest.

map $uri $legacy_target {
    default   "";
    /old      https://www.example.com/new/;
    /old/     https://www.example.com/new/;
}

server {
    listen 80;
    server_name example.com www.example.com;
    if ($legacy_target) { return 301 $legacy_target; }
    return 301 https://www.example.com$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;
    if ($legacy_target) { return 301 $legacy_target; }
    return 301 https://www.example.com$request_uri;
}

server {
    listen 443 ssl;
    server_name www.example.com;
    if ($legacy_target) { return 301 $legacy_target; }
    # site configuration
}

Bigger migrations? Same thing, just more lines. The approach to redirect maps for site moves scales this exact map to thousands of entries.

Testing and Maintaining the Flattened Rules

Run nginx -t, reload, then push the same curl list through again. Every old URL should now return one 301 and then a 200. That’s it. Next, update internal links, canonical tags and the sitemap to the final URLs, so the site stops generating fresh hops on its own. Keep a single redirect map file in version control. When a page moves again, edit the existing targets instead of piling a new rule on top (that’s exactly how the chain got there in the first place). With a reverse proxy in front, the redirects still live on the origin NGINX. Just make sure the proxy passes requests through so the origin sees the correct scheme and host. If it doesn’t, the rules match the wrong block.

Chains only stay fixed if someone actually owns them. Audit old URLs with curl, fold the scheme and www rules into one return, map legacy paths directly to their final addresses, and rerun the tests after every future move. Not glamorous, but it works.

FAQ

How many redirects can a chain have before it becomes a problem?

Aim for one hop per legacy URL. Every extra redirect is another request for users and crawlers, and you get nothing back for it. If the audit shows two or more hops, point that URL straight at its final address.

Can an http to https redirect chain and a www redirect be merged into one rule?

Yes. A single return 301 https://www.example.com$request_uri; in a block that catches plain http and the non-canonical host handles both at once. From any variant to the canonical URL in one step.

What is the difference between a redirect chain and a redirect loop?

A chain ends at a page after several hops. A loop sends the request back to an earlier URL and never ends. With curl -sIL, a chain shows several Location lines and then a 200. A loop just keeps repeating until curl gives up with a maximum redirects error.

ShareLinkedInX
Web Systems team

The team that builds and runs Jalvo.

Give each site its own IP.

Add your first domain in a few minutes.

Start now

Choose which cookies we may use. Necessary cookies keep the site and your login working and cannot be switched off.