Get the correct client IP behind trusted proxies in Nginx

Configure real-IP trust boundaries, understand recursive X-Forwarded-For parsing, and test spoofed headers from untrusted clients.

Best practices
Illustration of server operations and request logs

Use set_real_ip_from to identify the proxies allowed to supply client addresses, choose the header with real_ip_header, and enable recursive parsing when your trusted chain requires it. A forwarded header by itself is not proof of a client's identity.

Trust the actual proxy addresses

Inside http, for a hypothetical load balancer and proxy:

set_real_ip_from 10.20.0.10;
set_real_ip_from 10.20.0.11;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

Replace these with the precise addresses or controlled subnets of your real proxies. Avoid trusting all private address space merely because the addresses are private. Do not trust 0.0.0.0/0 or ::/0 on an internet-reachable listener.

The HTTP real-IP module must be available in your build. Inspect nginx -V and the installed package's module configuration if the directives are unknown.

Follow the trust chain from the connection inward

Suppose the path is client → edge proxy → load balancer → Nginx, and Nginx receives:

X-Forwarded-For: 203.0.113.20, 10.20.0.11

The immediate peer is 10.20.0.10. When that peer is trusted, recursive parsing works from the right through trusted addresses and selects the last nontrusted address as the effective client. In this example it selects 203.0.113.20 if both proxy addresses are trusted.

With recursion off, a trusted peer causes the last header address to be used, which may still be another proxy. If the immediate peer is untrusted, the module does not accept its header as an address replacement. $remote_addr becomes the selected address; $realip_remote_addr retains the original connection peer.

This produces a trusted interpretation of the proxy chain, not proof of an end user's personal identity. Every trusted proxy must overwrite or append headers according to the agreed design.

Verify both legitimate and spoofed requests

Use a restricted diagnostic endpoint or a temporary log format to record $remote_addr, $realip_remote_addr, and the received header. From an untrusted direct client:

curl -H 'X-Forwarded-For: 203.0.113.99' https://example.com/debug-ip

The effective address should remain the connection peer. Also test through the real trusted proxies, including multiple hops. A request from your laptop does not simulate a trusted load balancer unless its actual source address is in the configured trust set.

When forwarding to another application, define which header it should trust. $proxy_add_x_forwarded_for appends the current $remote_addr; after real-IP rewriting, the resulting chain can contain repeated client addresses. Do not assume it preserves a unique list of physical hops.

FAQ

Where should CDN address ranges come from?

Use the provider's official published ranges and keep them current. Ensure only that provider can reach an origin that trusts a provider-specific client-IP header.

Does the application also need trusted-proxy configuration?

If it interprets forwarded headers, yes. Nginx rewriting its own address variable does not automatically configure the framework's trust policy. Align the application with the exact headers and proxy path it receives.

Can I use CF-Connecting-IP or another header?

Yes, select the appropriate header with real_ip_header. Still restrict set_real_ip_from to the genuine provider or trusted intermediary that supplies it; custom header names do not prevent spoofing.

Reference: Nginx HTTP real-IP module.

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting