Cloudflare as a Transparent Proxy: Zero-Interference Edge Configuration

I use Cloudflare on most domain I run. But I don't let it make decisions for me. No content rewriting, no header injection, no protocol choices made on my behalf. Cloudflare is a DNS-driven CDN and a DDoS shield. The origin server — my VPS, my nginx config, my Let's Encrypt certificates — owns every security policy.

This isn't a philosophical stance. It's practical. When Cloudflare modifies your content or injects headers, you now have two places to debug when something breaks. When the origin is the single source of truth, there's exactly one place to look.

What you need first

  • A VPS running nginx with valid Let's Encrypt certificates on every domain
  • SSL/TLS already working at the origin (you can curl --resolve your domains from the server and get a clean HTTPS response)
  • Cloudflare proxying enabled (orange cloud) on your DNS records — this guide assumes traffic is already flowing through Cloudflare

If you haven't set up real visitor IP restoration yet, start with Restoring Real Visitor IPs with Cloudflare and Nginx. Every security feature covered here — rate limiting, fail2ban, access logs — depends on seeing the actual client address, not Cloudflare's proxy IPs.


The dashboard checklist: what to turn off

Cloudflare's dashboard has a lot of toggles. Most of them exist to compensate for origins that aren't pulling their own weight. If your origin is correctly configured — and the next section covers exactly what that means — you can turn nearly all of them off.

SSL/TLS

SSL/TLS Encryption Mode: Full (Strict)
Cloudflare validates the origin certificate. Flexible or non-Strict Full would accept any cert — including self-signed — defeating the purpose of having valid Let's Encrypt certificates on the origin.
Minimum TLS Version: 1.2
TLS 1.0 and 1.1 are deprecated, insecure, and cap SSL Labs grades at B. Setting 1.2 as the floor allows modern clients to negotiate up to 1.3 while still accommodating the vanishingly small number of legitimate 1.2-only clients.

Edge Certificates

HTTP Strict Transport Security: Off
The origin nginx server sends its own HSTS header. If Cloudflare also sends one, browsers receive duplicate headers. If max-age values differ between the two, the weaker one pollutes the policy. One authority, one header.

SSL/TLS → Edge Certificates also has Opportunistic Encryption and Automatic HTTPS Rewrites. Turn both off — the origin handles redirects and encryption. More on that in the hardening checklist.

Speed → Optimization

Auto Minify (CSS/JS/HTML): Off
Origin handles compression via gzip. Edge rewriting introduces bugs, breaks inline scripts, and makes debugging impossible — your minified file at the edge won't match what your server actually served.
Rocket Loader: Off
Injects JavaScript to alter script loading behavior. Interferes with theme and plugin asset loading. Not needed when the origin serves optimized assets with sensible Cache-Control headers.
Polish (Image Optimization): Off
Origin serves pre-optimized images. Edge recompression degrades quality and strips metadata you may have intentionally preserved. If you want WebP or AVIF, generate them at the origin — don't let Cloudflare guess.
Mirage (Mobile Image Optimization): Off
Same rationale as Polish. Origin is the authority on image delivery. For serving next-gen formats with content negotiation, see the WebP guide.

SSL/TLS → Edge Certificates

Always Use HTTPS: Off
The origin nginx server has an HTTP catch-all block that 301-redirects every request to HTTPS. Enabling this at Cloudflare adds a redundant second redirect hop and masks misconfigurations that should be fixed at the origin.
Automatic HTTPS Rewrites: Off
This scans HTML at the edge and rewrites http:// URLs to https://. If a WordPress post has a hardcoded http:// image, you want to find and fix it at the source, not have Cloudflare silently paper over it. The origin's redirects and HSTS already handle transport.

Security → Settings

Browser Integrity Check: Off
Blocks requests based on Cloudflare's client reputation database. Heavy-handed for most sites. Custom bot mitigation at the origin — fail2ban with nftables, rate limiting, a honeypot catch-all server block — is more precise and auditable. See the fail2ban guide for the full setup.

What Cloudflare still does

Even with everything above disabled, Cloudflare continues to provide:

  • DDoS protection — automatic, no configuration needed
  • Static asset caching — respects the origin's Cache-Control and Expires headers
  • Anycast DNS — routes visitors to the nearest edge node globally
  • Edge TLS termination — handles the TLS handshake close to the user, reducing latency

That's the point. You're not disabling Cloudflare. You're disabling Cloudflare's opinions about your content. The infrastructure — the global network, the DDoS scrubbing, the edge caching — all still work. They just work without second-guessing you.

A note for free-tier users

Some features can't be toggled off through the dashboard UI on the free plan — the switch is either missing or locked behind a plan upgrade prompt. The AI Agent in your Cloudflare account page (the chat interface in the dashboard sidebar) can reach settings the UI won't show you. Ask it directly: "I do not want any JavaScript code injection on my site. Please turn off Rocket Loader, and anything else that would embed JavaScript or other non-origin files." The AI identifies every zone with those features enabled and offers to disable them — it'll ask for confirmation through a modal before making changes. I used this to get Cloudflare as transparent as possible without upgrading my plan. It can't unlock paid features, but it can turn off free-tier features that the dashboard hides behind a paywall prompt.

Two things to enable: custom WAF rules

Nearly everything in the Cloudflare dashboard gets turned off. Two custom rules go in the opposite direction — they actively reduce load on the origin and prevent headaches during development.

Rule 1: Whitelist trusted IPs (Skip)

Create a list at Account → Manage Account → Configurations → Lists. Name it trusted_ips. Add your home/office IPv4, your home IPv6 as a /64 subnet (ISPs typically assign a full /64, and individual devices within that prefix rotate), and the VPS's own IPv4 and IPv6 addresses.

Then create a custom rule at Security → WAF → Custom Rules with this expression:

(ip.src in $trusted_ips)

Action: Skip. Check every WAF component in the skip options. Place this rule at the top of the list.

What it does: If you're developing from home, or if the server makes an outbound request that resolves through Cloudflare's proxy, the request bypasses all subsequent WAF rules — no managed challenges, no bot detection, no accidental blocks while you're fixing your own site. The origin's rate limiting and fail2ban still apply (you're hitting nginx directly), but Cloudflare's layer gets out of your way.

Rule 2: Managed Challenge on known bot targets

Your nginx deny all rules already drop requests for /wp-login.php, /xmlrpc.php, /.env, and the rest — the bot never reaches PHP. The wp_security.conf blocks in the WordPress installation guide, the bludit_security.conf rules in the Bludit installation guide, and the drop.conf snippet in Securing Nginx and PHP cover the full set. But nginx still has to accept the connection, parse the request, and return a 403. At scale, that's thousands of tiny tasks per day. Stopping the request at Cloudflare's edge means the connection never reaches your VPS at all.

Create a second custom rule with this expression:

(http.request.uri.path eq "/wp-login.php")
or (http.request.uri.path eq "/xmlrpc.php")
or (http.request.uri.path eq "/admin")
or (http.request.uri.path contains "/wp-admin/")
or (http.request.uri.path contains "/admin/")
or (http.request.uri.path eq "/readme.html")
or (http.request.uri.path eq "/license.txt")
or (http.request.uri.path eq "/.env")
or (http.request.uri.path eq "/.htaccess")
or (http.request.uri.path eq "/.DS_Store")
or (http.request.uri.path contains "../")
or (http.request.uri.path contains "/.git/")
or (http.request.uri.path contains "/cgi-bin/")
or (http.request.uri.path contains "/wp-content/uploads/"
    and http.request.uri.path contains ".php")
or (http.request.uri.path contains "/wp-content/"
    and http.request.uri.path contains ".php")
or (http.request.uri.path contains ".yml")
or (http.request.uri.path contains ".yaml")
or (http.request.uri.path contains ".config")
or (http.request.uri.path contains ".bak")
or (http.request.uri.path contains ".dist")
or (http.request.uri.path contains ".swp")
or (http.request.uri.path contains ".tfstate")
or (http.request.uri.path contains ".tfvars")

Action: Managed Challenge. Place this rule second in the list, right after the trusted IPs skip rule.

A Managed Challenge presents a browser checkpoint — JavaScript-based, non-interactive for real browsers, but a hard stop for the kind of automated scanner that doesn't render pages. Legitimate visitors to /wp-admin/ pass through transparently. Bots hitting /.env or /xmlrpc.php get dropped before your server ever sees the request.

This doesn't replace nginx-level blocking. It sits in front of it. The nginx rules are your defense-in-depth — if a request somehow clears the Cloudflare challenge, nginx still drops it. But for the 99% of bots that fail the challenge, your VPS does zero work.