Skip to content
How to make Laravel see real visitor IPs through Cloudflare — TrustProxies config, origin firewall rules, geolocation he...

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

Al Amin Ahamed

Al Amin Ahamed

Senior Software Engineer

· Updated 7 hours ago 8 min read

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.

PHP
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:

BASH
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:

BASH
# 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:

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

Or read the Cloudflare-specific header:

PHP
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):

PHP
$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:

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

Then post-process:

BASH
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
Al Amin Ahamed

Al Amin Ahamed

Senior software engineer & AI practitioner. 5+ years shipping Laravel platforms, WordPress plugins, WooCommerce extensions, and AI-driven products.

About me →

More from the blog

Need this kind of work shipped?

Available for freelance and consulting.

Laravel platforms, WordPress plugins, WooCommerce extensions, and AI integrations.