When configuring reverse proxies, edge CDNs, Single Page Applications (SPAs), or SSL termination in Nginx, web browsers may abruptly halt with the following client-side error:
ERR_TOO_MANY_REDIRECTS
Inspecting network headers reveals an infinite loop of HTTP 301 Moved Permanently or 308 Permanent Redirect status codes.
In production infrastructure, redirect loops are rarely caused by a single broken configuration line. They occur when two or more architectural layers disagree on the canonical URL representation—such as scheme (http vs https), host (example.com vs www.example.com), internal port (:8080 vs :443), or trailing slash (/app vs /app/).
In this guide, we break down Nginx's implicit directory-slash mechanics, reverse proxy URI stripping rules, Cloudflare Flexible SSL loops, and forwarded protocol headers, providing deterministic CLI diagnostics and production-ready configuration fixes.
[!TIP] Proactive Redirect & Header Verification Before pushing reverse proxy or trailing slash changes to production, trace your redirect hops and verify HTTP headers instantly using our free HTTP Header Checker, HTTP Status Code Checker, and Ping Test Tool.
The Mathematical Model of Redirect Loops
In HTTP architecture, canonicalization can be represented as a state transition function $f(\text{URL}) \to \text{URL}'$. A URL is represented by a 5-tuple:
$$\text{URL} = \langle \text{scheme}, \text{host}, \text{port}, \text{path}, \text{query} \rangle$$
A healthy infrastructure achieves a fixed point in at most one state transition:
$$f(\text{URL}{\text{initial}}) = \text{URL}{\text{canonical}} \quad \text{where} \quad f(\text{URL}{\text{canonical}}) = \text{URL}{\text{canonical}} \implies \text{HTTP } 200 \text{ OK}$$
A redirect loop occurs when the transformation function contains a non-terminating cycle:
$$f(\text{URL}_A) = \text{URL}_B \quad \text{and} \quad f(\text{URL}_B) = \text{URL}_A$$
+-------------------+ 301 Redirect (Append '/') +--------------------+
| /app (No Slash) | ----------------------------------> | /app/ (With Slash) |
| Nginx Directory | <---------------------------------- | Upstream App |
+-------------------+ 301 Redirect (Strip '/') +--------------------+
1. Root Cause: Nginx Implicit Directory Trailing Slashes
The most common internal redirect in Nginx is automatic directory canonicalization.
How Nginx Handles Directories
When a request arrives for /app:
- Nginx checks if a physical directory named
/var/www/html/app/exists on disk (viarootoralias). - If it is a directory and the requested URI lacks a trailing slash
/, Nginx automatically issues a 301 redirect to/app/to ensure relative links within the directory resolve correctly.
The Conflict with Application Routing
If your upstream backend framework (e.g., Express, Django, FastAPI) or API gateway is configured to strip trailing slashes (/app/ $\to$ /app), an infinite oscillation loop is created:
- Client requests
GET /app. - Nginx sees local directory
app/and returns301 Location: /app/. - Client requests
GET /app/. - Upstream application middleware rejects trailing slashes and returns
301 Location: /app. - Browser hits
ERR_TOO_MANY_REDIRECTS.
The Solution: Explicit Location Boundaries
To prevent Nginx from guessing based on filesystem state, declare explicit routing rules:
# Explicitly handle canonical slash redirect at the web server layer
location = /app {
return 301 /app/;
}
# Proxy the canonical trailing slash route
location /app/ {
proxy_pass http://backend_upstream;
}
2. proxy_pass Trailing Slash Semantics Matrix
A major source of routing loops and URI corruption is misunderstanding how the trailing slash on proxy_pass alters the request path forwarded to upstream servers.
Request: GET /api/v1/users
Case A: proxy_pass http://backend:8000; --> Upstream receives: /api/v1/users (Raw URI preserved)
Case B: proxy_pass http://backend:8000/; --> Upstream receives: /users (Prefix /api/v1/ replaced by /)
Complete Behavioral Matrix
| Location Block | proxy_pass Directive | Incoming Request | Upstream Receives | Architectural Use Case |
|---|---|---|---|---|
location /api/ | http://backend:8000; | /api/users | /api/users | Upstream expects the full /api/ prefix. |
location /api/ | http://backend:8000/; | /api/users | /users | Upstream is mounted at root /. |
location /app | http://backend:8000; | /app/login | /app/login | Raw path passthrough without prefix mutation. |
location /app | http://backend:8000/; | /app/login | //login (Bug!) | Mismatched slashes cause double-slash upstream errors. |
[!IMPORTANT] Slash Parity Rule: If your
locationblock contains a trailing slash (location /dir/), yourproxy_passURI replacement target should also contain a trailing slash (proxy_pass http://backend/).
3. Scheme Mismatches: Cloudflare & Load Balancers
The second major category of 301 redirect loops occurs at the edge-to-origin boundary.
[ Browser ] --( HTTPS:443 )--> [ Cloudflare / ALB ] --( HTTP:80 )--> [ Nginx Origin ]
|
Checks: $scheme == 'http'
|
Returns: 301 to HTTPS
v
[ Browser ] <--( 301 Location: https://example.com )-------------------------+
The "Flexible SSL" Loop Mechanism
- The client connects to Cloudflare over HTTPS.
- Cloudflare connects to your Nginx origin over plain HTTP on port
80(Standard Flexible SSL mode). - Nginx sees incoming traffic on port 80 where
$scheme == "http". - Nginx executes
return 301 https://$host$request_uri;. - Cloudflare receives the
301, forwards it to the browser, and the browser issues another HTTPS request to Cloudflare. - Cloudflare again queries Nginx over HTTP, repeating the cycle infinitely.
Solution A: Enable Full (Strict) SSL on Cloudflare
Switch Cloudflare encryption mode from Flexible to Full (Strict) so Cloudflare connects to your origin over HTTPS on port 443.
Solution B: Terminate HTTPS via $http_x_forwarded_proto
If Nginx sits behind AWS ALB, GCP Load Balancer, or Cloudflare and must listen on HTTP port 80, inspect the forwarded protocol header instead of $scheme:
# Map incoming X-Forwarded-Proto header to detect true client scheme
map $http_x_forwarded_proto $forwarded_scheme {
default $scheme;
https https;
http http;
}
server {
listen 80;
server_name example.com;
# Only redirect to HTTPS if the original client request was plain HTTP
if ($forwarded_scheme = "http") {
return 301 https://$host$request_uri;
}
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $forwarded_scheme;
proxy_pass http://app_cluster;
}
}
4. Internal Directives: absolute_redirect & port_in_redirect
When Nginx generates automatic redirects (e.g., appending a trailing slash to a directory), it constructs an absolute URL by default:
HTTP/1.1 301 Moved Permanently
Location: http://example.com:8080/app/
In multi-tier infrastructure where Nginx runs on internal port 8080 behind an edge proxy listening on 443, this leaks internal ports and downgrades HTTPS to HTTP.
Production Directives in nginx.conf
server {
listen 8080;
server_name example.com;
# Emit relative redirects (e.g., 'Location: /app/') instead of absolute URLs
absolute_redirect off;
# Do not append internal listening port (:8080) to generated redirects
port_in_redirect off;
location / {
try_files $uri $uri/ /index.html;
}
}
5. Single Page Application (SPA) Fallback Loops
In React, Vue, Next.js, and Angular deployments, developers frequently configure catch-all fallback routing:
# Problematic SPA Configuration
location / {
try_files $uri $uri/ /index.html;
}
location = /index.html {
return 301 /; # <-- Creates instant redirect loop with try_files!
}
When a user visits /dashboard:
try_fileschecks$uri(not found) and$uri/(not found).- It internally rewrites the request to
/index.html. - The
location = /index.htmlblock matches and issues301 /. - The client requests
/, triggering the loop again.
Robust SPA Production Configuration
Use the internal directive to ensure the fallback template is never invoked as an external redirect target:
server {
listen 443 ssl;
server_name example.com;
root /var/www/app/dist;
index index.html;
# Static assets cache directly
location /assets/ {
try_files $uri =404;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Internal fallback preventing external redirect oscillation
location = /index.html {
try_files $uri =404;
}
# SPA routing
location / {
try_files $uri $uri/ /index.html;
}
}
6. CLI Diagnostics & Packet Capture Sequence
Follow this CLI hierarchy to trace redirect chains and inspect raw protocol headers.
1. Trace the Full Redirect Chain with curl
# Follow redirects and print headers for every hop
curl -ILv https://example.com/app
# Inspect the exact first Location header without following
curl -sSI https://example.com/app | grep -iE 'HTTP/|location:'
Example diagnostic trace:
HTTP/2 301
location: https://example.com/app/
HTTP/2 301
location: https://example.com/app
HTTP/2 301
location: https://example.com/app/
This immediately confirms a trailing-slash canonicalization conflict.
2. Verify Full Expanded Configuration
Do not inspect isolated snippet files. Inspect the active, fully expanded runtime configuration:
sudo nginx -T | grep -nE 'return 30[18]|rewrite|proxy_pass|try_files|absolute_redirect|port_in_redirect'
3. Capture Origin HTTP GET Traffic with tcpdump
If debugging traffic arriving at your origin from an upstream load balancer or Cloudflare:
# Capture incoming HTTP GET requests on port 80
sudo tcpdump -nn -s0 -A 'tcp port 80 and (tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420)'
Look specifically for incoming X-Forwarded-Proto and Host headers to verify what the origin server receives.
Redirect Root Cause & Remediation Summary
| Symptom | Underlying Cause | Production Fix |
|---|---|---|
/app $\leftrightarrow$ /app/ Oscillation | Nginx directory check conflicts with app routing | Define explicit location = /app { return 301 /app/; } or set try_files $uri /index.html;. |
| HTTP $\leftrightarrow$ HTTPS Loop | Edge proxy terminates TLS but connects to origin on port 80 | Inspect $http_x_forwarded_proto or enable Full/Strict SSL at CDN edge. |
Port Leak (:8080 in redirect) | Internal listening port included in absolute redirect | Add absolute_redirect off; and port_in_redirect off; in server block. |
SPA Infinite 301 / Loop | try_files fallback matches a redirecting /index.html block | Remove external redirect on index.html or mark fallback internal;. |
Double Slash (//login) Upstream | Trailing slash missing on location but present on proxy_pass | Enforce slash parity between location /dir/ and proxy_pass http://backend/;. |
Full-Stack Edge & Uptime Monitoring with Pingzo
Redirect loops cause immediate traffic drops, broken SEO indexing, and degraded user experiences. Synthetic monitoring from multiple geographic edges detects redirect loop anomalies and header misconfigurations instantly before users report downtime.
+-----------------------------------------------------------------------------+
| Pingzo Global Edge Network |
+-----------------------------------------------------------------------------+
| |
[ Multi-Hop Redirect Audits ] [ Real-Time SSL & Header Probes ]
| |
v v
+-----------------------------------------------------------------------------+
| Your Web Server & Reverse Proxy Infrastructure |
| (Detects 301 Loops, 308 Loops, 502 Bad Gateway, 403 Errors) |
+-----------------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------------+
| Instant Multi-Channel Alerts: WhatsApp, Slack, & Telegram |
+-----------------------------------------------------------------------------+
Pingzo Monitoring Plans
| Feature | Free | Starter ($5/mo) | Pro ($12/mo) | Agency ($29/mo) |
|---|---|---|---|---|
| Check Frequency | 15 minutes | 5 minutes | 2 minutes | 1 minute |
| Monitors | 1 monitor | 10 monitors | Unlimited | Unlimited |
| Multi-Location Testing | Global | Global | Global | Global |
| SSL & DNS Audits | Basic | Advanced | Real-Time | Real-Time + Custom Root |
| Alert Channels | Email, Telegram, WhatsApp, Slack, Discord | All Channels + Multi-Recipient | Webhooks, SMS, White-Label Dashboards | |
| Multi-Tenant Management | No | No | No | Yes (Multi-Team) |
Detect redirect loops, protocol mismatches, and downtime instantly with Pingzo.
Stop Finding Out About Outages from Angry Users
Get instant WhatsApp & Discord alerts the second your API, website, or server goes down. Setup in 30 seconds with 60-second checks.