# ERR_TOO_MANY_REDIRECTS: How to Find and Fix a Redirect Loop

> This page isn't working. example.com redirected you too many times. ERR_TOO_MANY_REDIRECTS

Firefox calls it "The page isn't redirecting properly". Both mean the same thing: the browser followed redirects until it hit its limit (20 in Chrome and Firefox), because two or more rules keep sending it back and forth. Something says "go to B", and B says "go to A".

## Step 1: trace the loop

curl shows each hop without any browser cache in the way:

```bash
curl -sIL --max-redirs 10 http://example.com/ | grep -iE "^(HTTP|location)"
```

```text
HTTP/1.1 301 Moved Permanently
location: https://example.com/
HTTP/2 301
location: http://example.com/
HTTP/1.1 301 Moved Permanently
location: https://example.com/
```

Now you can see the circle — here, HTTP → HTTPS → HTTP. The pattern in the `location` lines tells you which kind of loop you have. The [HTTP Status Checker](/tools/web/http-status-checker) shows the same chain in the browser.

## The usual causes

### HTTPS redirect behind a proxy or CDN

The most common loop. Your app redirects HTTP to HTTPS, but it sits behind a load balancer or CDN that talks to it over plain HTTP. Every request arrives as HTTP, so the app redirects every request — forever.

- **Cloudflare**: with SSL mode **Flexible**, Cloudflare connects to your server over HTTP. If the server redirects to HTTPS, you loop. Switch to **Full (strict)** and install a certificate on the origin.
- **Your app behind a proxy**: tell the framework to trust the proxy's `X-Forwarded-Proto` header, so it knows the original request was HTTPS. In Express, `app.set("trust proxy", 1)`; in Django, `SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")`. See [X-Forwarded-For](/headers/x-forwarded-for) for how the forwarded headers work.

### www and apex rules fighting

One layer redirects `www.example.com` → `example.com` while another redirects `example.com` → `www.example.com` — often the hosting dashboard against an nginx or app rule. Pick one canonical host and make every layer agree.

### Trailing-slash rules

A framework setting adds a trailing slash (`/docs` → `/docs/`) while the server or CDN removes it. Same fix: decide on one form and remove the other rule.

### Login loops

A page requires authentication and redirects to `/login`; `/login` thinks you're logged in (or itself requires auth) and redirects back. Two common roots:

- The session cookie is never stored — for example it's marked `Secure` but the site runs on plain HTTP, or its `Domain` doesn't match. See [Set-Cookie](/headers/set-cookie).
- The cookie isn't sent on the request that matters — a cross-site POST back from an identity provider, blocked by `SameSite`. See [SameSite cookies explained](/blog/samesite-cookies-explained).

### CMS address settings

In WordPress, a mismatch between the "WordPress Address" and "Site Address" settings — `http://` against `https://`, or `www` against no `www` — loops the same way. Make both identical and match your real canonical URL.

## Step 2: rule out the browser's cache

A permanent redirect ([301](/http/301) or [308](/http/308)) can be cached by the browser indefinitely. After you fix the server, the browser may keep looping on its cached redirect. Test in a private window or with curl, and clear site data (DevTools → Application → Storage → Clear site data) for the real browser.

This is also why it's worth testing new redirects as [302](/http/302) first — see [301 vs 302 vs 307 vs 308](/blog/301-vs-302-vs-307-vs-308).

## Quick checklist

1. Trace the hops with `curl -sIL`.
2. Find the two rules that disagree — protocol, host, slash or auth.
3. Keep one rule; remove or align the other.
4. Retest in a private window.
