The minute you flip DNS over to a CDN, your origin access logs turn into a wall of identical proxy addresses. Every single visitor suddenly shares the same handful of edge IPs. That breaks geolocation, turns rate-limiting into a liability, and causes Fail2ban to knock out entire regions because a single bot triggered a jail rule. Here is how I configure Nginx and Apache to pull the genuine visitor IP out of StackPath’s edge headers and restore sanity to my server logs.

The Reverse Proxy Problem: Why Your Logs Break
StackPath sits between your users and your server as a reverse proxy. When someone hits your site, their browser connects directly to the nearest StackPath edge point of presence (PoP). If that edge node doesn’t have the cached response ready to go, it opens a fresh TCP connection back to your origin.
To Linux networking tools and your web server, the incoming client IP ($remote_addr in Nginx, %{c}a in Apache) is now StackPath’s proxy IP. That creates immediate headaches: an automated brute-force attack on /wp-login.php will trigger rate limiters that block the entire CDN node, killing access for hundreds of normal visitors behind that same edge. Even worse, automated tools like Fail2ban will ban whole CDN IP blocks at the firewall level.
StackPath passes the actual client IP along in specific HTTP headers on every request. You just have to tell your origin server which proxy subnets to trust and which headers to pull from.
StackPath Headers: X-Forwarded-For vs X-Real-IP
On every cache miss forwarded to your origin, StackPath sends along two key headers:
- X-Forwarded-For: A comma-separated chain of IPs showing the full request route:
client, proxy1, proxy2. - X-Real-IP: A single IP header containing the immediate client address that reached the CDN edge.
A rule: never trust these headers across the board without filtering by source IP. If an attacker bypasses your CDN and hits your origin server directly via its raw IP, they can fake any client address they want by passing a custom X-Forwarded-For header in curl. You must restrict your web server so it only parses these headers when the request comes from verified StackPath IP subnets.
Find and Maintain StackPath Edge CIDR Blocks
Before touching your server configs, grab the active IP CIDR blocks StackPath uses for its origin pulls. These ranges cover every edge node that could talk to your origin:
# StackPath IPv4 edge blocks
151.139.128.0/18
185.229.58.0/24
185.229.59.0/24
198.252.110.0/24
199.16.156.0/22 # StackPath IPv6 edge blocks
2a03:b0c0:1::/48
2a03:b0c0:2::/48
2a03:b0c0:3::/48If you run a local firewall like UFW, iptables, or firewalld, ensure these ranges can connect on ports 80 and 443. Also, if you run into redirect loops while routing traffic through the edge, check our guide on how to fix WordPress ERR_TOO_MANY_REDIRECTS behind StackPath CDN.
Configure Nginx with ngx_http_realip_module
Nginx handles this natively with http_realip_module. It rewrites $remote_addr and $remote_port in memory to match the header value, which automatically fixes your access logs and passes the right IP down to backend apps through FastCGI.
Check for Module Support
First, verify your Nginx binary was compiled with the module:
nginx -V 2>&1 | grep --color=auto 'http_realip_module'Modern distros (Ubuntu, Debian, AlmaLinux) include this by default in their standard packages. If running that command produces blank output, install nginx-extras or compile Nginx with --with-http_realip_module enabled.
Create the StackPath Real-IP Config File
Keep your configs clean by tossing the proxy directives into a dedicated snippet instead of dumping them all into nginx.conf:
sudo nano /etc/nginx/conf.d/stackpath-realip.confDrop in the following directives. The set_real_ip_from lines tell Nginx which source IPs to trust, while real_ip_header points Nginx to the incoming header holding the client address:
# Trust StackPath IPv4 ranges
set_real_ip_from 151.139.128.0/18;
set_real_ip_from 185.229.58.0/24;
set_real_ip_from 185.229.59.0/24;
set_real_ip_from 198.252.110.0/24;
set_real_ip_from 199.16.156.0/22; # Trust StackPath IPv6 ranges
set_real_ip_from 2a03:b0c0:1::/48;
set_real_ip_from 2a03:b0c0:2::/48;
set_real_ip_from 2a03:b0c0:3::/48; # Read IP from header and allow recursive searches across proxies
real_ip_header X-Forwarded-For;
real_ip_recursive on;Why use real_ip_recursive on;? If a visitor is using their own proxy or VPN before hitting StackPath, X-Forwarded-For will look like client_ip, user_proxy_ip. With recursion enabled, Nginx parses from right to left, strips out every trusted IP defined in your set_real_ip_from directives, and settles on the last untrusted address as the real visitor.
Check the config syntax and reload Nginx:
sudo nginx -t && sudo systemctl reload nginxIf you’re serving static assets or webfonts through the CDN on Nginx, see how to fix CORS font errors behind StackPath CDN.
Configure Apache with mod_remoteip
On Apache 2.4 and newer, the tool for the job is mod_remoteip. It swaps the connection IP address with the one found in the request headers.
Enable the Apache Module
On Ubuntu and Debian, enable it with a2enmod:
sudo a2enmod remoteipOn RHEL, Rocky, or AlmaLinux, verify that LoadModule remoteip_module modules/mod_remoteip.so is active inside /etc/httpd/conf.modules.d/00-base.conf.
Add StackPath Proxy Directives
Create a dedicated config file for the remote IP directives:
sudo nano /etc/apache2/conf-available/remoteip-stackpath.confAdd the trusted proxy directives:
RemoteIPHeader X-Forwarded-For RemoteIPTrustedProxy 151.139.128.0/18 RemoteIPTrustedProxy 185.229.58.0/24 RemoteIPTrustedProxy 185.229.59.0/24 RemoteIPTrustedProxy 198.252.110.0/24 RemoteIPTrustedProxy 199.16.156.0/22 RemoteIPTrustedProxy 2a03:b0c0:1::/48 RemoteIPTrustedProxy 2a03:b0c0:2::/48 RemoteIPTrustedProxy 2a03:b0c0:3::/48
Enable the config and test for syntax errors:
sudo a2enconf remoteip-stackpath
sudo apache2ctl configtest
sudo systemctl restart apache2Update Apache LogFormat
Here is an easy mistake to make on Apache: the default LogFormat uses %h (remote host), which stubbornly keeps logging the proxy IP on some Apache builds. Switch that over to %a (client IP) so it prints the rewritten address.
Open /etc/apache2/apache2.conf (or your primary vhost) and update the combined log format:
# Replace %h with %a
LogFormat "%a %l %u %t "%r" %>s %O "%{Referer}i" "%{User-Agent}i"" combinedReload Apache to apply the format change:
sudo systemctl reload apache2If you’re pairing Apache with custom browser caching rules, check our walkthrough on how to fix static asset cache policies in .htaccess.
Testing the Setup: What I Ran to Verify the Fix
Don’t just open a browser and hope for the best. Test your server with curl from the command line to verify both spoof protection and live edge forwarding.
Test 1: Direct Request (Simulating Spoofing)
Hit your origin IP directly from your local terminal and inject a bogus IP (like Cloudflare’s 1.1.1.1) into the header:
curl -I -H "X-Forwarded-For: 1.1.1.1" https://yourdomain.com/Now check your origin’s access log:
tail -n 1 /var/log/nginx/access.logYou should see your actual local public IP in the log, not 1.1.1.1. Because your computer’s IP isn’t in the trusted StackPath subnet list, Nginx ignores the forged header completely. Spoof protection works.
Test 2: Live Request Through StackPath
Now request your live site through your regular domain routing via StackPath, while tailing your logs:
tail -f /var/log/nginx/access.logYou should immediately see your genuine public IP in the log stream instead of an edge address starting with 151.139.x.x. Grab your current IP from icanhazip.com if you need to double-check.
Gotchas: WordPress, PHP-FPM, and Cloud Firewalls
Even after setting up web server modules, a couple of common runtime quirks can still trip you up.
PHP and WordPress $_SERVER[‘REMOTE_ADDR’]
With PHP-FPM running behind Nginx, your FastCGI configuration passes $remote_addr down to PHP like this:
fastcgi_param REMOTE_ADDR $remote_addr;Since ngx_http_realip_module rewrites $remote_addr directly in memory before passing execution to FastCGI, PHP picks up the real client IP in $_SERVER['REMOTE_ADDR'] out of the box. You do not need plugins like “Real-IP Fixer” if the web server handles it cleanly.
If you run dynamic WordPress pages behind the edge, make sure you know how to bypass StackPath CDN cache for logged-in WordPress users.
Cloud Firewalls (UFW / AWS Security Groups)
Keep this in mind: http_realip_module and mod_remoteip live strictly at the application layer (Layer 7). They do not change Layer 3 or 4 TCP connections. If you try to drop a visitor with a UFW rule like this:
# This will NOT work as expected behind a CDN
sudo ufw deny from 203.0.113.50The Linux kernel firewall only evaluates the raw packet source IP, which is still the StackPath edge node. To block abusive visitors at the server level once they pass the CDN, use Nginx deny directives (which read the rewritten $remote_addr) or fail2ban filters configured against your application logs.
Frequently Asked Questions
What happens if StackPath adds new IP addresses?
If traffic routes through a new subnet that isn’t listed in your set_real_ip_from or RemoteIPTrustedProxy directives, your web server simply logs the CDN IP for those requests. Keep an eye on the official StackPath Support Knowledge Base occasionally, or automate a script to diff their published lists.
Should I use X-Real-IP or X-Forwarded-For?
X-Forwarded-For paired with real_ip_recursive on; is generally the safer bet because it handles multi-hop proxy chains while filtering out known intermediary nodes. X-Real-IP works fine in simple setups, but it only contains a single IP and can be overwritten if traffic passes through multiple proxies.
Will this fix WooCommerce fraud detection and geo-location?
Yes. Plugins like WooCommerce Geolocation check $_SERVER['REMOTE_ADDR']. As soon as Nginx or Apache overwrites that variable with the genuine client address, geolocation checks and checkout fraud detection start resolving your customers instead of CDN data centers.
Why does Fail2ban keep banning StackPath edge nodes?
Fail2ban monitors raw log files. If your LogFormat or Nginx access logging was still recording CDN edge IPs when Fail2ban fired, it grabbed those addresses and blocked them via iptables. Fix your web server logs first, verify real IPs are appearing with tail -f, and unban the edge IPs before restarting Fail2ban.
Next Steps
With real IPs printing cleanly to your logs, fail2ban filters, rate limiting, and analytics will start behaving as intended. If you want to refine how your CDN handles incoming URLs, read our guide on how to ignore query strings in StackPath CDN edge rules to bump your cache hit rates without breaking dynamic requests.

