Four sites this week. Same bug on all four. The site worked in a browser (sort of — Chrome quietly hides redirect loops in the network panel unless you look). In curl -L, it hit the 50-redirect ceiling and aborted. The culprit, every time, was a Netlify redirect rule that looked like this:
[[redirects]]
from = "/glossary"
to = "/glossary/"
status = 301
force = true
The intent is clear: normalize /glossary (no trailing slash) to /glossary/ (trailing slash). The trailing-slash URL is where the real page lives. This rule should send curl /glossary to /glossary/ once, serve the page, and be done. With force = true, it sends curl /glossary/ to /glossary/ every time, forever, until curl gives up.
What force = true Actually Does
Netlify's redirect engine runs in two modes depending on whether force = true is set.
Without force (default). Netlify first checks if a real file exists at the requested path. If yes, serve the file. If no, evaluate redirect rules. This means redirect rules are fallbacks — they fire when nothing else matches. This is the behavior most people expect.
With force = true. Netlify evaluates the redirect rule before checking for files. If the rule matches, the redirect fires. If the destination also matches a redirect rule, that rule fires too. And so on.
The bug surfaces when you use force on a rule whose to value matches the rule's own from pattern. The slash-normalizing case is the classic example. Consider from = "/glossary" with force = true. The from pattern in Netlify matches prefix-style, and /glossary/ technically starts with /glossary. Depending on your pretty_urls setting and how Netlify parses the rule, the redirect can fire on the normalized URL too, sending /glossary/ to /glossary/ in an infinite loop.
The Curl Symptom
$ curl -IL https://example.com/glossary
HTTP/2 301
location: https://example.com/glossary/
HTTP/2 301
location: https://example.com/glossary/
HTTP/2 301
location: https://example.com/glossary/
[...]
curl: (47) Maximum (50) redirects followed
Fifty identical 301s with the same Location header. The browser often hides this because Chromium's redirect detection is forgiving — it may silently serve the destination after a handful of hops — but curl, wget, and most AI retrieval engines do not. They hit the limit and give up.
For an AI citation engine specifically, this is catastrophic. The bot fetches /glossary/, gets redirected to /glossary/, retries, gets redirected again, flags the URL as unreachable, and moves on. Your glossary page is invisible to AI retrieval even though it renders fine in a browser.
Why Force Feels Useful
The force = true flag gets added because someone read the Netlify docs and learned that redirect rules are normally skipped when a real file exists. Then they hit a scenario where the rule needs to fire even though the file exists — for example, redirecting a deprecated page to a new one when the old file is still in the build output. Force is the right answer there.
The problem is that people then copy the force = true pattern to other rules where it's not needed. Slash-normalizing rules are the most common victim. The thinking goes "I want to make sure this redirect always fires" which is not actually what force means. Force means "fire this redirect even if the destination file exists." On a slash-normalizing rule, the destination file is exactly what you want to serve — that's the whole point of the redirect.
The Correct Pattern
Slash normalization is what Netlify's pretty_urls setting is for. Set it once in netlify.toml and let Netlify handle it:
[build.processing]
pretty_urls = true
With pretty URLs enabled, Netlify automatically redirects /glossary to /glossary/ (or vice versa, depending on your build output). No explicit redirect rule needed. No force flag. No loop.
If for some reason you need an explicit rule, write it without force:
[[redirects]]
from = "/glossary"
to = "/glossary/"
status = 301
Netlify will check if /glossary/ exists as a real file. It does. It serves the file. One hop. No loop.
When force = true Is Actually Correct
Force is the right answer when you need a redirect to fire even though a file exists at the destination path. Two real examples:
1. Legacy content override. You have an old page at /about-us.html in your build output that you can't remove for cache reasons, but you want traffic to go to /about/. A forced redirect overrides the static file:
[[redirects]]
from = "/about-us.html"
to = "/about/"
status = 301
force = true
2. Geo or auth-gated content. You want a page to serve different content to different users based on a header or cookie. The default Netlify behavior of "serve the file if it exists" would bypass your logic. Force ensures the rule fires every time:
[[redirects]]
from = "/app"
to = "/login/"
status = 302
force = true
conditions = {Cookie = ["!authenticated"]}
In both cases, the destination is different from the source pattern in a way that prevents self-reference. No loop risk.
The Audit Check
Every site I run now gets a redirect-loop check as part of deploy-preview. The bash one-liner is:
for url in /glossary/ /about/ /tools/ /contact/; do
code=$(curl -s -o /dev/null -w "%{http_code}" \
-L --max-redirs 3 "https://example.com$url")
echo "$url -> $code"
done
If any URL reports a non-2xx code, I check the full redirect chain with curl -IL and look for repeating Location headers. If the same URL appears twice in the chain, something is looping.
I bake this into the post-deploy notification on every site in the network now. Catching a loop at deploy time is an order of magnitude cheaper than catching it weeks later when AI citations stop coming in.
Why I Saw This on Four Sites at Once
I had cloned a netlify.toml template across the network months ago. The template included a redirect block with force = true on the slash-normalizing rule. I didn't notice at the time because the sites all rendered fine in a browser. The Link Graph tool caught it last week when it started flagging /glossary/ on every site as "unreachable (redirect loop)." Four sites, same bug, same fix.
The fix was one line per site: delete force = true from the slash-normalizing rule. Commit, deploy, re-run the audit. Clean.
The Short Version
force = trueon a Netlify redirect makes the rule fire even if the destination file exists. That's the whole difference from the default.- On slash-normalizing rules (
/glossaryto/glossary/), force creates a self-referential loop.curl -Lhits the 50-redirect limit; AI retrieval engines give up. - Remove
force = truefrom any redirect whosetovalue is a variant of thefrompattern. - Use
[build.processing] pretty_urls = truefor automatic slash normalization. No rule needed. - Keep force only when you're overriding an actual static file on disk or gating content by condition.
- Add a post-deploy curl loop check to catch this at deploy time.