Restore Real Visitor IP Addresses Behind StackPath CDN on Nginx and Apache

by Fahim

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.

Server terminal display showing Nginx real IP configuration and access log entries
Server terminal display showing Nginx real IP configuration and access log entries

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::/48

If 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.conf

Drop 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 nginx

If 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 remoteip

On 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.conf

Add 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 apache2

Update 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"" combined

Reload Apache to apply the format change:

sudo systemctl reload apache2

If 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.log

You 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.log

You 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.50

The 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.

all_in_one_marketing_tool