☀️ Columbus 11°C
// How social engineering cost Marks & Spencer £300 Million // The Backdoor (CVE-2024-3094): 500ms Delay that Saved the Internet From Disaster // PostgreSQL Normalization: The Easy Way vs. The Correct Way (1NF, 2NF, 3NF) // SQLite in Depth: Concurrency & Locked Error // How to Restore True Visitor IPs Behind Cloudflare Using NGINX and cf‑nginx (2026) // Understanding Network Layers: Complete Guide to OSI and TCP/IP Models (2026) // Networking Infrastructure: The Complete Guide From Topologies to Security (2026) // Automated Ubuntu VM Creation on Proxmox via Cloud-Init (2026) // Python Virtual Environments (venv) on Windows and Linux (2026) // How to Secure SSH on Debian 11 & 12 with User Creation and Fail2Ban (2026) // How to Set Up an Isolated VM Network in Proxmox with NAT (Step-by-Step) (2026) // How to Configure Locales on Debian (Fix Language and Encoding Issues)
9 min 177

How to Restore True Visitor IPs Behind Cloudflare Using NGINX and cf‑nginx (2026)

Automatically get real visitor IPs behind Cloudflare with cf-nginx. Fix analytics, rate limiting, and geolocation without manual updates or code changes.

cloudflare

If you’re running a website behind Cloudflare proxy, you’ve probably noticed something frustrating: your server logs show Cloudflare’s IP addresses instead of your actual visitors’ IPs. This breaks analytics, rate limiting, geolocation, and pretty much anything that depends on knowing where your traffic actually comes from.

I built cf-nginx to fix this problem automatically. But before we get into the solution, let’s talk about what Cloudflare actually does and why this issue exists in the first place.

What is Cloudflare?

Cloudflare is a content delivery network (CDN) and web security service that sits between your website visitors and your server. When someone visits your site, they don’t connect directly to your server anymore – they connect to Cloudflare’s network first.

Think of it like a bouncer at a club. Instead of people walking straight into your venue, they check in with the bouncer first. The bouncer can:

  • Block troublemakers (DDoS attacks, bots)
  • Check IDs (security challenges)
  • Keep track of who’s inside (analytics)
  • Even cache your content so people get faster access

Over 20 million websites use Cloudflare, from personal blogs to major companies. It’s popular because the basic tier is free and surprisingly powerful.

How Cloudflare’s Proxy Works

When you “orange cloud” a domain in Cloudflare (turn on the proxy), here’s what happens:

Without Cloudflare:

Visitor → Your Server

With Cloudflare:

Visitor → Cloudflare Network → Your Server

Cloudflare intercepts all requests, processes them through their security and caching layers, then forwards the legitimate traffic to your origin server. Your actual server IP stays hidden – visitors only see Cloudflare’s IPs.

This is great for security. Attackers can’t DDoS your server directly if they don’t know where it is. But it creates a problem.

The IP Address Problem

When Cloudflare forwards requests to your server, those requests come from Cloudflare’s IP addresses, not from your actual visitors. Your server logs look like this:

104.21.34.56 - - [10/Feb/2026] "GET /page HTTP/1.1"
172.64.155.23 - - [10/Feb/2026] "GET /api HTTP/1.1"
104.21.34.56 - - [10/Feb/2026] "POST /form HTTP/1.1"

Those are all Cloudflare IPs. You have no idea who your real visitors are.

This breaks:

Analytics – You can’t tell which countries your traffic comes from
Rate limiting – You can’t block abusive users by IP
Access control – You can’t restrict content by geography
Security logs – You can’t investigate attacks properly
Application logic – Anything that depends on visitor IPs fails

Some people try to solve this by reading the X-Forwarded-For header in their application code. That works, but you have to modify every application. And it doesn’t help your web server logs, system monitoring, or anything else that reads the connection IP directly.

The Right Solution: NGINX Real IP Module

NGINX has a built-in module called ngx_http_realip_module that solves this properly. You tell NGINX which IP addresses to trust (Cloudflare’s), and it rewrites the connection IP to show the real visitor.

You configure it like this:

set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
# ... all Cloudflare IP ranges
real_ip_header CF-Connecting-IP;

The CF-Connecting-IP header is sent by Cloudflare with every request and contains the original visitor IP.

Now your logs show real visitor IPs:

198.51.100.45 - - [10/Feb/2026] "GET /page HTTP/1.1"
203.0.113.67 - - [10/Feb/2026] "GET /api HTTP/1.1"
198.51.100.45 - - [10/Feb/2026] "POST /form HTTP/1.1"

Everything works. Your application sees real IPs without any code changes. Your logs are accurate. Your rate limiting works. Problem solved.

Except there’s a catch.

The Maintenance Problem

Cloudflare doesn’t use fixed IP addresses. They add and remove IP ranges as they expand their network. If you hardcode their IPs in your NGINX config and Cloudflare adds a new range, requests from that new range won’t get the real IP treatment.

You have to:

  1. Check Cloudflare’s IP list regularly
  2. Update your NGINX config when it changes
  3. Test the config
  4. Reload NGINX
  5. Do this for every site you manage

Cloudflare publishes their IPs at https://api.cloudflare.com/client/v4/ips, but checking it manually is tedious. Most people set it up once and forget about it, then wonder months later why some of their traffic shows Cloudflare IPs again.

Why cf-nginx Exists

I got tired of updating Cloudflare IPs manually across multiple servers. I’d forget, then realize weeks later that my rate limiting wasn’t working because new Cloudflare ranges weren’t in my config.

So I built cf-nginx to automate the entire process:

  • Fetches the latest Cloudflare IPs from their API
  • Updates your NGINX configs automatically
  • Tests the config before applying (safe)
  • Rolls back if something breaks
  • Runs daily to catch any changes
  • Handles both HTTP and HTTPS configs
  • Works with Let’s Encrypt/certbot setups

One command:

sudo cf-nginx enable yoursite.com

It asks if you want SSL (if you don’t have it), adds the Cloudflare IP directives to all your server blocks, tests everything, and enables automatic daily updates. Done.

Who Should Use This

You need cf-nginx if:

  • You’re running websites behind Cloudflare’s proxy
  • You’re using NGINX as your web server
  • You care about seeing real visitor IPs
  • You don’t want to manually maintain Cloudflare’s IP list

You don’t need it if:

  • You’re not using Cloudflare’s proxy (orange cloud off)
  • You’re using Apache, Caddy, or another web server (this is NGINX-specific)
  • You’re okay with seeing Cloudflare IPs everywhere

How It Works Technically

cf-nginx is a Debian package that installs:

  1. Main command (cf-nginx) – Manages configuration
  2. Validator (cf-nginx-validate) – Checks your setup
  3. Systemd timer – Runs daily updates at 3am
  4. Library functions – Handles API calls and config updates

When you enable it for a site:

sudo cf-nginx enable example.com

Here’s what happens:

  1. Checks if SSL is configured (offers to install Let’s Encrypt if not)
  2. Fetches current Cloudflare IPs from their API
  3. Finds your NGINX config file
  4. Backs it up
  5. Inserts the real IP directives after each server_name line
  6. Runs nginx -t to test the config
  7. If test passes, reloads NGINX
  8. If test fails, restores the backup automatically
  9. Adds the site to the auto-update list

The daily timer checks if Cloudflare’s IPs changed and updates all your enabled sites automatically. You never have to think about it again.

Installation and Basic Usage

Install the package:

https://github.com/cfunkz/cf-nginx <- Repository lives here

wget https://github.com/cfunkz/cf-nginx/releases/download/v1.0.1/cf-nginx.deb
sudo dpkg -i cf-nginx.deb

Enable for your site:

sudo cf-nginx enable yoursite.com

That’s it. Check it worked:

sudo cf-nginx-validate yoursite.com

View status:

sudo cf-nginx status

The auto-update timer is enabled by default. Check it:

systemctl status cf-nginx.timer

Update/Sync your firewall settings with the Cloudflare list:

sudo cf-nginx ufw-enable
sudo cf-nginx update

That’s it, set it & forget it! CF-NGINX should do rest of the work.

Real-World Example

Let’s say you run a WordPress site behind Cloudflare. Without cf-nginx, your logs show only Cloudflare IPs. If security plugins aren’t configured to read headers correctly, they can’t block attackers by IP, and analytics can’t geolocate visitors accurately.

You install cf-nginx:

sudo cf-nginx enable myblog.com

It detects if you already have SSL (from Let’s Encrypt) or prompts for enabling, adds the Cloudflare IP directives to both your HTTP and HTTPS server blocks, tests the config, and reloads NGINX.

Now WordPress sees real visitor IPs. Your security plugin can block by IP. Your analytics show real geographic data. Everything just works.

Three months later, Cloudflare adds a new IP range for their Asia expansion. At 3am, cf-nginx’s timer runs, detects the new IPs, updates your config, tests it, and reloads NGINX. You never even know it happened.

Comparison with Other Solutions

Manual Updates

  • Free
  • You forget to do it
  • Time-consuming
  • Error-prone

Application-Level Fixes

  • Requires code changes
  • Doesn’t fix logs
  • Breaks if you forget to check the header
  • Needs changes in every app

cf-nginx

  • One-time setup
  • Automatic updates
  • No code changes needed
  • Works for everything (logs, apps, monitoring)

Security Considerations

cf-nginx only trusts IPs from Cloudflare’s official API. This is important because if you trust the wrong IPs, attackers could spoof the X-Forwarded-For header and fake their real IP.

The real_ip_header CF-Connecting-IP directive tells NGINX to trust Cloudflare’s header, but only for requests coming from Cloudflare’s IP ranges. Requests from other IPs don’t get this treatment, so they can’t spoof their origin.

The daily updates ensure you’re always trusting the current Cloudflare network, not outdated ranges that might have been reassigned.

Common Issues and Solutions

“I’m seeing both Cloudflare IPs and real IPs”

Your config probably has Cloudflare IPs in some server blocks but not others. Run:

sudo cf-nginx-validate yoursite.com

It’ll tell you if directives are missing from any server blocks.

“Auto-updates aren’t working”

Check the timer status:

systemctl status cf-nginx.timer

View the logs:

journalctl -u cf-nginx -n 50

“I want to remove cf-nginx”

sudo cf-nginx disable yoursite.com  # Remove from one site
sudo apt-get remove cf-nginx        # Uninstall completely

Your NGINX configs stay intact either way.

When You Shouldn’t Use Cloudflare’s Proxy

Cloudflare’s proxy is great for most websites, but there are cases where you shouldn’t use it:

  • WebSocket-heavy applications (adds latency)
  • Large file downloads (can hit bandwidth limits on free tier)
  • Services that need true end-to-end encryption
  • Applications where you need the visitor’s real IP for legal compliance (though cf-nginx solves this)

If you’re just using Cloudflare for DNS (gray cloud, not orange), you don’t need cf-nginx because visitors connect directly to your server.

The Bottom Line

Cloudflare’s proxy is incredibly useful for security and performance, but it hides your visitors’ real IP addresses. This breaks a lot of things that depend on knowing where traffic comes from.

The proper solution is NGINX’s real IP module, but maintaining Cloudflare’s IP list manually is tedious and error-prone.

cf-nginx automates this completely. Install it once, enable it for your sites, and forget about it. It’ll keep your configs up to date automatically.

If you’re running NGINX behind Cloudflare and you care about seeing real visitor IPs (and you should), cf-nginx solves a real problem you probably didn’t know had such a simple solution.

5 3 votes
Article Rating

Related Posts

5 3 votes
Article Rating
Subscribe
Notify of
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments