Wrong Visitor IP in cPanel Logs? Fix It Behind Cloudflare

· 5 min read · 4 views · Getwebup

Turn on Cloudflare's orange cloud and suddenly every visitor on your server looks like the same handful of IP addresses. Your AWStats reports show one "user" generating half your traffic. Fail2Ban bans a Cloudflare data-center IP and locks out your entire site. A WordPress commenter's IP shows up as 104.x.x.x instead of their real address. None of this is a bug in your hosting - it's what happens when a reverse proxy sits in front of your server and nothing on the origin side has been told to look past it.

Symptom: every request looks like it's coming from Cloudflare

Once a domain is proxied through Cloudflare (or any CDN/reverse proxy), the TCP connection to your VPS or cPanel server originates from Cloudflare's edge network, not from the visitor's browser. That means:

  • $_SERVER['REMOTE_ADDR'] in PHP returns a Cloudflare IP, not the visitor's IP.
  • Apache/Nginx access logs and AWStats/Webalizer in cPanel show a small pool of repeating Cloudflare IPs instead of real visitors.
  • WordPress plugins that log IP (comment forms, contact forms, login logs, security plugins) record the wrong address.
  • cPanel's IP Blocker, cPHulk, or a WHM firewall rule aimed at a specific abusive visitor does nothing, because that visitor's real IP never touches your server.
  • Worst case: Fail2Ban or CSF sees a spike of "bad" requests from a Cloudflare IP and bans it - taking down access for every visitor behind that edge node, including legitimate traffic.

Cause: your origin server never unwraps the real client IP

Cloudflare (and most CDNs) forward the visitor's real IP in an HTTP header - usually CF-Connecting-IP, alongside the more general X-Forwarded-For. But Apache, Nginx, PHP, and every tool built on top of them (AWStats, WordPress, Fail2Ban) only trust REMOTE_ADDR, the actual TCP peer address, unless you explicitly configure them to read that header instead - and only when the connection genuinely comes from Cloudflare's published IP ranges.

Skipping that "only trust it from Cloudflare's ranges" part is the part people get wrong when they try to fix this with a plugin alone: trusting X-Forwarded-For blindly lets anyone spoof their IP by sending a fake header directly to your server's IP, bypassing Cloudflare entirely.

Fix 1: restore the real IP at the web server level (cPanel/WHM, Apache)

This is the fix that makes AWStats, Webalizer, cPanel's raw access logs, and cPHulk all see the correct IP at once, because it rewrites REMOTE_ADDR before anything else touches the request.

  1. In WHM, go to Software > EasyApache 4 and confirm mod_remoteip is installed (on CloudLinux servers it's usually already available as mod_cloudflare or bundled with CageFS-aware Apache builds).
  2. Configure it with Cloudflare's current IP ranges, pulled fresh from https://www.cloudflare.com/ips-v4 and https://www.cloudflare.com/ips-v6 - don't hardcode an old list, Cloudflare adds ranges occasionally.
  3. Set the trusted header to CF-Connecting-IP (more reliable than the generic X-Forwarded-For, which can contain a chain of proxies).
  4. Restart Apache: whmapi1 restartservice httpd or via WHM > Restart Services.

If you're on Nginx (a reverse proxy in front of Apache, or a standalone LEMP VPS), the equivalent is the ngx_http_realip_module:

set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
# ... one line per Cloudflare range ...
real_ip_header CF-Connecting-IP;
real_ip_recursive on;

Put this in http {} or a dedicated conf.d/cloudflare.conf file, then nginx -t && systemctl reload nginx.

Fix 2: make WordPress see the real IP too

Fixing Apache/Nginx fixes REMOTE_ADDR at the OS level, which covers most cases. But some WordPress plugins read $_SERVER['HTTP_CF_CONNECTING_IP'] or HTTP_X_FORWARDED_FOR directly instead of relying on REMOTE_ADDR. For those, a small must-use plugin is the cleanest fix - it runs before every other plugin and keeps the logic in one place instead of scattered across theme functions:

<?php
// wp-content/mu-plugins/restore-real-ip.php
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
    $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CF_CONNECTING_IP'];
}

Only add this if you've already restricted access at the web-server level to Cloudflare's ranges (Fix 1) - otherwise a visitor who bypasses Cloudflare and hits your origin IP directly could set that header themselves and spoof any address they like.

Fix 3: stop Fail2Ban and CSF from banning Cloudflare instead of the attacker

If Fail2Ban or CSF/LFD is watching Apache's access log and that log still shows Cloudflare IPs (because Fix 1 hasn't been applied yet, or a jail reads raw connection data), it will eventually ban a Cloudflare edge node - and that ban blocks every visitor routed through it, not just the attacker.

SymptomLikely causeFix
Whole site suddenly unreachable for many visitors at onceCSF/Fail2Ban banned a Cloudflare IP rangeWhitelist Cloudflare's ranges in CSF's csf.pignore/csf.allow, or exclude them in the Fail2Ban jail
Bans "work" but the attacker keeps getting throughFail2Ban is reading REMOTE_ADDR before Fix 1 is appliedApply Fix 1 first, then bans will target the real attacker IP
cPHulk locks out a shared office/mobile IP repeatedlyMultiple attackers or scanners share one Cloudflare-facing IP pool upstream (e.g. carrier-grade NAT), unrelated to CloudflareCheck WHM > cPHulk Brute Force Protection logs for the real originating IP once headers are trusted

In WHM, add Cloudflare's published ranges to CSF's ignore list under ConfigServer Security & Firewall > csf.pignore so LFD never auto-bans them, while still letting you manually block a genuinely abusive real IP once you can see it.

Prevention

  • Re-pull Cloudflare's IP range list every few months - curl https://www.cloudflare.com/ips-v4 - and update your Apache/Nginx trusted-proxy config; ranges do change.
  • Never trust X-Forwarded-For or CF-Connecting-IP from a server that also accepts direct traffic on its origin IP. Lock the firewall (CSF/UFW) so port 80/443 only accepts connections from Cloudflare's ranges plus your own known IPs.
  • After any EasyApache 4 rebuild, confirm mod_remoteip is still enabled - profile rebuilds can silently drop modules that weren't in the saved profile.
  • Test with a quick check: visit your site through Cloudflare from your phone (mobile data, not home Wi-Fi) and confirm the IP in your latest access log entry matches your phone's actual public IP, not a 104.x/172.x Cloudflare address.

Questions people actually ask