Updated October 2026: Added real_ip_recursive for proxy chains, an IP-spoofing warning, corrected the AWS (ALB/NLB, PROXY protocol) and Google Cloud guidance, and replaced moved or misleading documentation links.

In the realm of web servers, accurate logging of client IP addresses is crucial for security, analytics, and compliance purposes. However, when Nginx is deployed behind a reverse proxy, load balancer, or CDN, the IP addresses logged may not reflect the actual client IP addresses. This is where the Nginx Real IP module comes into play.

I. What is Nginx Real IP Module?

The Nginx Real IP module (ngx_http_realip_module) is an extension for Nginx that allows the server to obtain the real client IP address from the headers forwarded by a proxy or load balancer. It replaces $remote_addr with that address, so the correct client IP addresses are logged in server access logs and used by every other module (access rules, rate limiting, geo lookups). The original connection address stays available as $realip_remote_addr.

II. Why Use Nginx Real IP Module?

  1. Accurate Logging: By obtaining the real client IP address, the Nginx Real IP module ensures that server access logs accurately reflect the origins of incoming requests. This is essential for security analysis, troubleshooting, and compliance audits.

  2. IP-based Access Control: Many applications rely on IP-based access control to restrict access to certain resources. The Nginx Real IP module enables precise enforcement of access control rules based on the actual client IP address.

  3. Geolocation and Analytics: Accurate client IP addresses are necessary for geolocation services and analytics tools to provide insights into user demographics, behavior, and traffic patterns.

III. Installation and Configuration

To enable the Nginx Real IP module, follow these steps:

  1. Install Nginx with Real IP Module: Ensure that Nginx is compiled with the Real IP module. It is not built by default when you compile from source, but most distribution packages and the official nginx.org packages include it. Check with nginx -V 2>&1 | grep -o with-http_realip_module. If you’re compiling Nginx from source, use the --with-http_realip_module option.

  2. Configure Nginx: Add the following configuration to your http or server block:

    set_real_ip_from <IP_Range>;      # repeat for every trusted proxy range (IPv4 and IPv6)
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;
    

    Replace <IP_Range> with the IP address range of your proxy, load balancer, or CDN.

  3. Reload Nginx: After making the changes, test the configuration and reload Nginx. A reload applies the new configuration without dropping open connections, so a full restart is not needed:

    sudo nginx -t && sudo systemctl reload nginx
    

1. How real_ip_recursive handles proxy chains

X-Forwarded-For is a comma-separated list: every proxy appends the address it received the connection from. With a chain like client → CDN → load balancer → nginx, the header that reaches nginx looks like:

X-Forwarded-For: <client>, <cdn-edge>

and the TCP connection itself comes from the load balancer.

  • With real_ip_recursive off (the default), nginx takes the last address in the header as soon as the connection comes from a trusted proxy. In the chain above that is the CDN edge, not the client.
  • With real_ip_recursive on, nginx walks the list from right to left, skipping every address that matches a set_real_ip_from range, and uses the first untrusted address. If both the load balancer and the CDN ranges are trusted, that is the real client.

So for any setup with more than one proxy in front of nginx, trust every hop and turn real_ip_recursive on.

2. Security warning: do not trust X-Forwarded-For blindly

X-Forwarded-For is just a request header, and any client can send one. If you trust too much, an attacker can make nginx log, rate-limit and allow/deny requests as any IP they like:

  • Only list proxy ranges you control (or that your CDN/cloud provider officially publishes) in set_real_ip_from. Never use set_real_ip_from 0.0.0.0/0; or ::/0.
  • Never take the leftmost address of X-Forwarded-For as the client IP. The leftmost entries are whatever the client sent; only the entries added by your own proxies are trustworthy, which is exactly what real_ip_recursive evaluates.
  • Restrict direct access to the origin. If clients can bypass the CDN or load balancer and reach nginx directly, firewall them off (security group, firewall rules) so that only your proxies can connect.

IV. Integrating with Cloudflare, AWS, and Google Cloud

1. Cloudflare

If your Nginx server is behind Cloudflare, you can use the CF-Connecting-IP header, which contains a single value (the visitor’s IP) instead of a list:

set_real_ip_from 173.245.48.0/20;   # one line per Cloudflare range ...
set_real_ip_from 2400:cb00::/32;    # ... including the IPv6 ranges
real_ip_header CF-Connecting-IP;

Add a set_real_ip_from line for every range Cloudflare publishes, both IPv4 and IPv6. Cloudflare publishes the current lists on the Cloudflare IP Ranges page (plain-text versions at https://www.cloudflare.com/ips-v4 and https://www.cloudflare.com/ips-v6). The lists change over time, so regenerate this part of the configuration from those URLs regularly (for example from a cron job or your deployment tooling) instead of copying them once. See also Cloudflare’s guide on restoring original visitor IPs.

2. Amazon Web Services (AWS)

An Application Load Balancer (ALB) passes the client address in X-Forwarded-For. By default the ALB appends the client IP to any existing header, so a forged value sent by the client stays on the left and the ALB’s entry is the last one:

set_real_ip_from <VPC_CIDR>;        # e.g. 10.0.0.0/16, or the CIDRs of the ALB's subnets
real_ip_header X-Forwarded-For;
real_ip_recursive on;

An ALB has no fixed IP addresses: its nodes run inside the subnets you attach it to and their private IPs change as the load balancer scales. There is no fixed range to copy into set_real_ip_from. Use your VPC CIDR (or the CIDRs of the load balancer’s subnets) as the trusted range, and keep instances reachable only from the load balancer’s security group.

Network Load Balancer (NLB) works at layer 4 and does not add HTTP headers. With instance targets the client IP is preserved by default, so nginx already sees it as $remote_addr. When it is not preserved (for example IP targets with TCP/TLS, where client IP preservation is off by default), enable Proxy Protocol v2 on the target group (proxy_protocol_v2.enabled) and let nginx read the address from it:

server {
    listen 80 proxy_protocol;

    set_real_ip_from <VPC_CIDR>;
    real_ip_header proxy_protocol;
}

Only enable proxy_protocol on a listen that receives traffic exclusively from the NLB: every connection on that port must start with a PROXY protocol header, including the NLB’s health checks.

If CloudFront sits in front of your origin, trust its origin-facing ranges as well (keep real_ip_recursive on). AWS publishes all of its IP ranges, including CloudFront’s, in ip-ranges.json:

3. Google Cloud

If your Nginx server is behind a Google Cloud external Application Load Balancer, use the X-Forwarded-For header. The load balancer appends two addresses to it: the client IP and then the IP of the load balancer’s forwarding rule (your load balancer’s own IP address):

X-Forwarded-For: <existing-value>,<client-ip>,<load-balancer-ip>

The TCP connection to your backend comes from Google Front Ends (GFEs). For the global external and classic Application Load Balancers with instance-group or zonal NEG backends, those use 130.211.0.0/22 and 35.191.0.0/16. Trust both the GFE ranges and your load balancer’s IP so that real_ip_recursive skips past them to the client:

set_real_ip_from 130.211.0.0/22;
set_real_ip_from 35.191.0.0/16;
set_real_ip_from <Load_Balancer_IP>;   # the forwarding rule's external IP
real_ip_header X-Forwarded-For;
real_ip_recursive on;

Without real_ip_recursive on (or without trusting the load balancer’s IP), nginx would log the load balancer’s address instead of the client’s. Anything to the left of <client-ip> was supplied by the client and is not verified by Google. For the regional external Application Load Balancer, connections come from the proxy-only subnet you configured, so trust that subnet instead of the GFE ranges.

Visit the following Google Cloud documentation pages for the ranges and header format:

By incorporating the Nginx Real IP module and configuring it with the correct IP ranges of your proxy, load balancer, or CDN, you can ensure accurate client IP address logging in your Nginx server logs. This enhances security, access control, and analytics capabilities for your web applications, making it an essential component of your web server setup, as long as you trust only the proxies you actually run behind.