← Back to Blog

Netlify force = true and the Redirect Loop You Ship By Accident

Netlify force = true and the Redirect Loop You Ship By Accident

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 = true on 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 (/glossary to /glossary/), force creates a self-referential loop. curl -L hits the 50-redirect limit; AI retrieval engines give up.
  • Remove force = true from any redirect whose to value is a variant of the from pattern.
  • Use [build.processing] pretty_urls = true for 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.

← Back to Blog

Accessibility Options

Text Size
High Contrast
Reduce Motion
Reading Guide
Link Highlighting
Accessibility Statement

J.A. Watte is committed to ensuring digital accessibility for people with disabilities. This site conforms to WCAG 2.1 and 2.2 Level AA guidelines.

Measures Taken

  • Semantic HTML with proper heading hierarchy
  • ARIA labels and roles for interactive components
  • Color contrast ratios meeting WCAG AA (4.5:1)
  • Full keyboard navigation support
  • Skip navigation link
  • Visible focus indicators (3:1 contrast)
  • 44px minimum touch/click targets
  • Dark/light theme with system preference detection
  • Responsive design for all devices
  • Reduced motion support (CSS + toggle)
  • Text size customization (14px–20px)
  • Print stylesheet

Feedback

Contact: jwatte.com/contact

Full Accessibility StatementPrivacy Policy

Last updated: April 2026