nginx cloudflare ip-restriction 403

Why Nginx Allow Doesn't Work Through Cloudflare

When going through Cloudflare, the source IP becomes Cloudflare's reverse proxy instead of the actual client, so Nginx's allow/deny doesn't work as expected and 403 errors occur. This article explains the XFF handling and configuration pitfalls.

Shou Arisaka
3 min read
Nov 27, 2025

When accessing Nginx through Cloudflare, IP restrictions with allow/deny directives may not work as expected. This article explains the cause and solution for cases where you want to allow access only from specific IPs, but all requests get 403 errors.

Why Allow Doesn’t Work

When going through Cloudflare, the source IP seen by Nginx is not the client’s actual IP but Cloudflare’s reverse proxy server IP.

# This configuration won't work properly
allow 192.0.2.1;  # your IP
deny all;

The above configuration intends to “allow access only from 192.0.2.1”, but the source IP reaching Nginx is actually Cloudflare’s IP (e.g., 104.16.x.x). Therefore, all requests match deny all and return 403 Forbidden.

Solution: Use real_ip_header

Nginx has a feature to use the actual client IP instead of the reverse proxy IP. For Cloudflare, the actual client IP is stored in the CF-Connecting-IP header.

# Trust Cloudflare IP ranges
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;

# IPv6 if needed
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;

# Get actual IP from Cloudflare header
real_ip_header CF-Connecting-IP;

With this, $remote_addr will contain the client’s actual IP, and allow/deny will work correctly.

X-Forwarded-For (XFF) Pitfalls

Using the X-Forwarded-For header is also possible, but requires caution.

real_ip_header X-Forwarded-For;
real_ip_recursive on;

XFF headers accumulate multiple IPs comma-separated when passing through multiple proxies. Also, clients can spoof this header.

Setting real_ip_recursive on excludes trusted proxy IPs and uses the last untrusted IP, but misconfiguration can create security holes.

For Cloudflare, using CF-Connecting-IP is more reliable.

Verifying Configuration

To verify the configuration is working correctly, check if the actual IP is recorded in access logs.

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent"';

If your IP appears in $remote_addr, the configuration is working correctly. If Cloudflare’s IP appears, review the set_real_ip_from configuration.

Updating Cloudflare IP Ranges

Cloudflare IP ranges change occasionally. The latest IP ranges can be found on the official page (https://www.cloudflare.com/ips/).

Updating periodically or creating an automation script to update Nginx configuration provides peace of mind.

Summary

The reason Nginx allow/deny doesn’t work through Cloudflare is that the source IP becomes Cloudflare’s IP. By configuring set_real_ip_from and real_ip_header CF-Connecting-IP, access control based on actual client IP becomes possible.

Share this article

Shou Arisaka Nov 27, 2025

🔗 Copy Links