Cloudflare + Reverse Proxy Origin Pitfalls: 308 Loops / 522 Black Holes / 429 Rate Limiting

Background

To accelerate multiple websites using Cloudflare, the architecture is: Cloudflare → Edge reverse proxy server (Nginx) → Origin server. During implementation, we encountered three consecutive pitfalls—all related to Cloudflare’s origin-fetching mechanism. We document them here for future troubleshooting.

Pitfall 1: Cloudflare Flexible SSL + Origin 308 Redirect = Infinite Redirect Loop

Symptom: After enabling Cloudflare Proxy (orange-cloud), browsers report ERR_TOO_MANY_REDIRECTS.

Cause: When Cloudflare SSL mode is set to Flexible, Cloudflare initiates origin requests over HTTP port 80. However, the origin Nginx server redirects all HTTP requests to HTTPS using a 308 Permanent Redirect:

if ($scheme = http) {
    return 308 https://$host$request_uri;
}

Cloudflare receives the 308 response and follows the redirect—issuing another HTTP request to the origin—which again triggers the 308, resulting in an infinite loop.

Fix: Configure the origin server to detect Cloudflare traffic and skip the redirect. Cloudflare includes the X-Forwarded-Proto header in origin requests; use it for conditional logic:

# Composite variable: only redirect if request is HTTP *and* not from Cloudflare
set $do_redirect "";
if ($scheme = http) {
    set $do_redirect "H";
}
if ($http_x_forwarded_proto = "") {
    set $do_redirect "${do_redirect}N";
}
if ($do_redirect = "HN") {
    return 308 https://$host$request_uri;
}

Logic: $scheme = http (H) + missing X-Forwarded-Proto (N) = direct (non-Cloudflare) access → trigger redirect. Since Cloudflare does send X-Forwarded-Proto, the condition evaluates to false and the 308 is skipped.

Nginx Tip: Nginx if does not support logical operators like AND/OR. Concatenating flags via set is a widely adopted community pattern to simulate compound conditions.

Alternative Fix: Install Cloudflare’s Origin Certificate on the origin server and switch Cloudflare SSL mode to Full. This enables Cloudflare to connect to the origin over HTTPS. However, certificate management adds operational overhead—adjusting the Nginx condition is simpler.

Pitfall 2: Cloudflare for SaaS Fallback Origin Points to Blackhole IP → 522 Error

Symptom: DNS has been pointed to Cloudflare and the proxy is enabled (orange-cloud), yet visitors receive HTTP 522 (Connection timed out).

Cause: When using Cloudflare for SaaS (SSL for SaaS), the fallback origin’s A record was mistakenly set to 192.0.2.1. Per RFC 5737, this is a documentation-only reserved address (TEST-NET-1)—it is non-routable and effectively a black hole. Cloudflare attempts to connect to this IP and times out.

Fix: Update the fallback origin’s A record to point to the origin server’s actual public IP address.

Technical Note: RFC 5737 defines three documentation-only IPv4 address blocks:
192.0.2.0/24 (TEST-NET-1),
198.51.100.0/24 (TEST-NET-2),
203.0.113.0/24 (TEST-NET-3).
If you see any of these IPs in DNS records, they are almost certainly unconfigured placeholders.

Pitfall 3: Reverse Proxy Fails to Forward Real Client IP → Backend 429 Rate Limiting

Symptom: When accessing the site via Cloudflare, some static assets (CSS/JS) return HTTP 429 (Too Many Requests); direct access to the origin works fine.

Cause: Although the reverse proxy Nginx config appears correct—using set_real_ip_from and real_ip_header CF-Connecting-IP—the default Nginx package on Ubuntu/Debian does not compile the ngx_http_realip_module. Consequently, those directives silently fail.

Result: $remote_addr remains Cloudflare’s edge node IP, and X-Real-IP passed to the backend is also Cloudflare’s IP. The backend application (here, Discourse) treats all users as a single IP, triggering rate limiting.

Verify Module Availability:

nginx -V 2>&1 | grep -o 'real_ip'
# Output present → module compiled; no output → module missing

Fix: Bypass the realip module entirely and directly forward Cloudflare’s header:

proxy_set_header X-Real-IP $http_cf_connecting_ip;
proxy_set_header X-Forwarded-For $http_cf_connecting_ip;

$http_cf_connecting_ip is an auto-populated Nginx variable derived from the incoming CF-Connecting-IP header—no extra modules required.

Security Note: If the reverse proxy accepts traffic not exclusively from Cloudflare, the CF-Connecting-IP header can be spoofed by clients. To mitigate, restrict inbound access to your reverse proxy’s ports 80/443 using a firewall, allowing only Cloudflare’s IP ranges.

Troubleshooting Checklist

When issues arise after integrating Cloudflare, follow this diagnostic order:

Symptom Checkpoint
Redirect loop Does the origin unconditionally redirect HTTP→HTTPS? Is Cloudflare SSL mode Flexible?
522 timeout Does the DNS A record point to a real IP? Was the SaaS fallback origin misconfigured?
429 rate limiting Does the reverse proxy correctly forward the real client IP? Is the realip module available?
520 unknown error Does the origin return a response Cloudflare cannot parse? Check origin server error logs.
525 SSL handshake failure With Full SSL mode, does the origin lack a valid certificate? Switch to Flexible or install an Origin Certificate.