Skip to content

Cloudflare in Front of Laravel: Real IPs, Trusted Proxies, Locked-Down Origins

A

Al Amin Ahamed

Senior Engineer

8 min read
𝕏 in

Cloudflare in front of Laravel breaks request()->ip() — every request appears to come from Cloudflare's edge IPs. Sessions still work, but rate limiting, geolocation, and audit logs all see the wrong IP. Here's the correct fix.

The Problem

Without Cloudflare:

Browser (203.0.113.42) → Server request()->ip() = '203.0.113.42'

With Cloudflare:

Browser (203.0.113.42) → Cloudflare (172.69.0.X) → Server request()->ip() = '172.69.0.X' ❌ wrong

Cloudflare adds a CF-Connecting-IP header with the real IP. Laravel needs to know it can trust that header.

The Fix — TrustProxies

Laravel's App\Http\Middleware\TrustProxies middleware controls which proxies are trusted and which header to read. By default it's restrictive.

namespace App\Http\Middleware; use Illuminate\Http\Middleware\TrustProxies as Middleware; use Illuminate\Http\Request; class TrustProxies extends Middleware { protected $proxies = '*'; // Trust all proxies — only safe behind Cloudflare protected $headers = Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO; }

'*' looks dangerous but is correct if and only if your origin server is firewalled to only accept traffic from Cloudflare. If anyone can hit your origin directly, an attacker can spoof X-Forwarded-For and bypass IP-based controls.

Lock Down the Origin

Get Cloudflare's IP ranges:

curl https://www.cloudflare.com/ips-v4 -o /tmp/cf-ips-v4 curl https://www.cloudflare.com/ips-v6 -o /tmp/cf-ips-v6

UFW rules:

# Allow Cloudflare IPs while read ip; do sudo ufw allow from "$ip" to any port 443 done < /tmp/cf-ips-v4 # Block direct origin access sudo ufw deny 80 sudo ufw deny 443 sudo ufw enable

Now your origin only accepts HTTPS from Cloudflare. Direct hits get dropped.

Read the Real IP

After TrustProxies runs:

request()->ip(); // 203.0.113.42 ✓

Or read the Cloudflare-specific header:

request()->header('CF-Connecting-IP'); // 203.0.113.42

I prefer request()->ip() because it works whether you're behind Cloudflare, AWS ALB, or no proxy.

Geolocation

Cloudflare exposes geolocation in headers (free tier includes country):

$country = request()->header('CF-IPCountry'); // 'US', 'BD', 'UK'

For paid plans, you also get city, postal code, and continent. No third-party geo-IP service needed.

Real Visitor Country in Logs

Add the Cloudflare IP to your access log format. In Caddy:

log { output file /var/log/caddy/access.log format json { time_format iso8601 message_key msg } }

Then post-process:

jq '.request.headers."CF-Connecting-IP"[0]' /var/log/caddy/access.log

Cloudflare-Specific Quirks

Bot Fight Mode. Cloudflare blocks "obviously bot" traffic before it hits your origin. The first time a search engine indexes your site after enabling this, indexing stops. Disable for /sitemap.xml and /robots.txt:

Page Rules: /sitemap.xml → Security Level: Essentially Off /robots.txt → Security Level: Essentially Off

Caching dynamic content. Cloudflare caches by URL by default — including pages that depend on session state. A logged-out visitor's homepage gets cached and served to logged-in users. Either:

  • Disable caching for HTML (Page Rule: * → Cache Level: Bypass)
  • Or set Cache-Control: private on session-aware pages

Argo Smart Routing. The $5/month upgrade routes traffic over Cloudflare's backbone instead of public internet. For users in regions far from your origin, p99 latency drops 30-50%. Worth it for global apps.

What I Skip

  • Cloudflare Access for staging — overkill for personal projects, use HTTP basic auth in Caddy instead.
  • Workers in front of Laravel — adds latency and complexity for marginal gain.
  • R2 for assets — public/build is small enough that serving from origin is fine.

The one Cloudflare feature I always enable: HTTP/3. Free, faster, and connection migration over wifi switches is genuinely useful.

Share 𝕏 in
A

Al Amin Ahamed

Senior software engineer & AI practitioner. Laravel, PHP, WordPress plugins, WooCommerce extensions.

About me →

One email a month. No noise.

What I shipped, what I read, occasional deep dive. Unsubscribe anytime.