Fixing Redirect Loops on Cloudflare Pages
"This page isn't working — docs.example.com redirected you too many times." A redirect loop on a static site is maddening, because nothing in the site's code redirects anything: the loop lives in the interaction between redirect rules in different places. On Cloudflare Pages there are at least four such places — the project's _redirects file, Pages' own trailing-slash handling, the zone's Page Rules or Redirect Rules, and the zone's SSL setting — and two of them agreeing to disagree produces an infinite loop.
This guide works through three loops on a marketing site and docs on Cloudflare Pages that appeared over a few months, traces each with curl, and fixes the rule causing it. It belongs to Cloudflare Pages Edge Caching Setup; the redirect configuration itself is covered in Configuring Redirects on Cloudflare Pages.
Trace the Loop First
Guessing at redirect rules wastes time. One command shows every hop:
curl -sIL --max-redirs 8 https://example.com/docs/install 2>&1 | grep -iE '^(HTTP|location|server|cf-ray)'
HTTP/2 308
location: /docs/install/
HTTP/2 301
location: https://example.com/docs/install
HTTP/2 308
location: /docs/install/
...
curl: (47) Maximum (8) redirects followed
Two URLs alternating, with two different status codes, means two different rules. The 308 is characteristic of Pages' trailing-slash handling; the 301 came from somewhere else.
Loop 1: _redirects Fighting Pages' Trailing-Slash Handling
The site had moved from Netlify, whose _redirects contained a rule to strip trailing slashes:
/*/ /:splat 301
On Pages, a request for /docs/install finds docs/install/index.html and Pages redirects it to /docs/install/ with a 308 — directory index files get a trailing slash. The old rule then redirected /docs/install/ back to /docs/install. Loop.
Fix: delete the rule and let Pages handle slashes. Pages' behaviour follows file names: about/index.html is served at /about/, about.html at /about. To change which form is canonical, change how the build writes files (Astro's build.format, Hugo's uglyURLs), not the redirects. Links in the site should use the same form, as in Clean URLs and Trailing Slashes on S3, which covers the same choice on another host.
Loop 2: Apex and www Redirected Both Ways
Weeks later, https://example.com looped while https://www.example.com worked. The trace:
HTTP/2 301 location: https://www.example.com/ (server: cloudflare, from a Redirect Rule)
HTTP/2 301 location: https://example.com/ (from _redirects)
A colleague had added a zone-level Redirect Rule sending the apex to www for a marketing campaign. The project's _redirects already had a host-based rule doing the opposite (https://www.example.com/* https://example.com/:splat 301). Zone Redirect Rules run before the request reaches Pages, so both fired, one on each hop.
Fix: choose one canonical host and one place for host redirects. The team kept the apex as canonical and removed the zone Redirect Rule. Host-level redirects now live only in the zone's Redirect Rules (documented in the repository's README) and _redirects contains only path redirects — the split prevents the next person from adding the opposite rule in the other place.
Loop 3: Flexible SSL on a Proxied Origin
The third loop appeared only on a subdomain, status.example.com, which was not on Pages but a small VM behind Cloudflare's proxy, linked from the site. The zone's SSL mode was Flexible: Cloudflare connects to the origin over HTTP. The VM's Nginx redirected all HTTP to HTTPS. So: browser → Cloudflare (HTTPS) → origin (HTTP) → 301 to HTTPS → browser → Cloudflare (HTTPS) → origin (HTTP) → 301…
Fix: set SSL to Full (strict) and install a certificate on the origin (a free Cloudflare Origin CA certificate works), so Cloudflare connects over HTTPS and the origin's redirect never fires. Pages projects themselves are not affected by this setting's loop, but a Flexible zone setting will catch any proxied origin on the same zone.
Clearing Cached Loops
After each fix, some readers still reported the loop. Browsers had cached the 301s. The fixes were verified with curl and private windows, and for the duration of future redirect experiments the team uses 302 or 307 until the rule is confirmed, switching to 301 or 308 afterwards. A cached permanent redirect cannot be recalled from a reader's browser.
Loops That Involve Workers or Functions
Sites that add Pages Functions or a Worker in front can create the same pattern in code: middleware that normalises paths (lowercasing, adding slashes) while the asset layer normalises them differently. The rule is the same — one place decides the canonical form. On this site the middleware only handles language redirects, and it checks new URL(request.url).pathname against the canonical form before redirecting, so it never issues a redirect to a URL the asset layer would redirect back.
Reading Which Product Sent a Redirect
The response headers on each hop usually say where it came from. Redirects issued by Pages' asset handling are 308s with no extra headers; zone Redirect Rules and Page Rules return 301 or 302 with server: cloudflare but before any Pages-specific headers; redirects from _redirects carry the project's custom headers if _headers applies to the path; and origin redirects behind a proxied zone include the origin's own server header, such as nginx. Comparing those on two consecutive hops identified the two rules in all three loops within minutes.
Prevent the Next Loop
A CI check runs curl traces against a list of canonical URLs in both host forms, with and without trailing slashes, on every preview deploy, and fails if any chain is longer than two hops or ends in anything but 200:
while read u; do
hops=$(curl -sIL --max-redirs 5 -o /dev/null -w '%{num_redirects} %{http_code}' "$u")
case "$hops" in 0\ 200|1\ 200|2\ 200) ;; *) echo "BAD $u -> $hops"; fail=1;; esac
done < redirect-cases.txt; exit ${fail:-0}
Zone-level rules are not part of preview deploys, so the same script also runs against production on a schedule. Testing Redirects and Headers Before Deploy has the fuller version.
Measured Impact
| Measure | During loops | After fixes | Tool |
|---|---|---|---|
| URLs looping (of 40 tested) | 40 / 3 / 1 (per incident) | 0 | curl trace script |
| Max redirect hops to a canonical URL | ∞ | 1 | curl trace script |
| "Redirect error" in Search Console | 1,240 URLs | 0 after recrawl | Page indexing report |
Rules in _redirects | 212 | 186 (host and slash rules removed) | File |
Pitfalls & Rollback
- Carrying trailing-slash rules over from another host. Pages handles slashes from file names.
- Host redirects in two places. Keep them in zone Redirect Rules only.
- Flexible SSL with an HTTPS-redirecting origin. Use Full (strict).
- Testing with permanent redirects. Use 302/307 until confirmed.
- Rollback: each fix removes or changes one rule; a removed rule can be restored, but trace it with
curlbefore and after.
Conclusion
Redirect loops on Cloudflare Pages come from two rules in different places that disagree: a _redirects slash rule against Pages' own handling, host redirects in both the zone and the project, or Flexible SSL against an origin that redirects to HTTPS. Tracing every hop with curl, giving each kind of redirect one home, and checking redirect chains in CI and on a schedule has kept every URL variant within two hops of its page.
FAQ
Why does my Cloudflare Pages site show too many redirects?
Two or more rules keep sending the browser between URLs. Common pairs on Pages are a _redirects rule that adds or removes a trailing slash fighting Pages' own trailing-slash handling, a redirect from apex to www in Page Rules with the opposite in _redirects, and HTTP to HTTPS redirects combined with Flexible SSL on a proxied origin. Tracing the redirect chain with curl shows which rules are involved.
How do I trace a redirect loop?
Run curl with the location option and a maximum number of redirects, printing each status and location, for example curl -sIL --max-redirs 10 and the URL. The output lists every hop. A loop shows the same two URLs alternating; the headers on each hop often reveal which product issued it.
Does Cloudflare Pages add trailing slashes itself?
Yes. Pages serves about slash index.html at both slash about and slash about slash, and redirects between them with a 308 depending on how the file is named. A _redirects rule that redirects in the opposite direction creates a loop. Let Pages handle trailing slashes and remove such rules.
Can a redirect loop be cached?
Yes. Browsers cache 301 and 308 redirects, sometimes for a long time, so a loop can persist for readers even after it is fixed on the server. Test fixes in a private window or with curl, and prefer 302 or 307 while experimenting with redirect rules.
Related
- Parent: Cloudflare Pages Edge Caching Setup — static hosting on Cloudflare.
- Configuring Redirects on Cloudflare Pages — the
_redirectsfile in depth. - Caching 404s and Redirects at the Edge — how redirects are cached.
- Netlify Redirects and Rewrites for Static Sites — where the slash rule came from.
- Custom Domains and TLS on Cloudflare Pages — SSL modes and hosts.
Filed underFixesCloudflare