Operations 9 min read

Capture Real Client IP Behind Multiple Proxies Using HAProxy & Nginx Ingress

This guide explains how to retrieve the true client IP in environments with multiple proxy layers by understanding remote_addr, X-Forwarded-For, and X-Real-IP headers, and configuring HAProxy and Nginx Ingress with appropriate options to forward and preserve the original IP.

Ops Development Stories
Ops Development Stories
Ops Development Stories
Capture Real Client IP Behind Multiple Proxies Using HAProxy & Nginx Ingress

Background Information

Because the product requires obtaining the real client IP on the application side, the request chain passes through multiple proxies, making it impossible to get the true IP by default.

Basic Concepts

Before implementation, understand the common headers used to obtain IP addresses.

remote_addr

X-Forwarded-For

X-Real-IP

(1) remote_addr

remote_addr

represents the client IP, but its value is set by the server based on the client’s connection. If there is no proxy,

remote_addr

is the host IP. When a proxy is present,

remote_addr

becomes the proxy’s IP unless the proxy manually sets it to the original client IP.

(2) X-Forwarded-For

X-Forwarded-For

(XFF) is an HTTP extension header. Its value consists of multiple IPs separated by commas and spaces, ordered from the farthest device to the nearest proxy, e.g.,

X-Forwarded-For: client,proxy1,proxy2

. Note that the XFF header can be forged.

PS: The format of X-Forwarded-For can be spoofed.

If an application sits behind three proxies (Proxy1, Proxy2, Proxy3) with IPs IP1, IP2, IP3 and the real client IP is IP0, the XFF header received by the application should be:

X-Forwarded-For: IP0,IP1,IP2

. IP3 is omitted because Proxy3 forwards Proxy2’s request, adding Proxy2’s IP to XFF, while the client IP is placed in

remote_addr

.

PS: Only one proxy adds the previous node’s IP to XFF .

(3) X-Real-IP

X-Real-IP

is a custom header usually set by HTTP proxies to indicate the IP of the device that established the TCP connection. Unlike XFF, it is not a list; each proxy replaces the previous value instead of appending.

Ideally, the application should receive

X-Real-IP

as the client’s real IP, meaning only the first proxy sets this header while subsequent proxies simply forward the request.

Implementation Details

In practice, the first layer (SLB) is a pure TCP proxy, so no extra configuration is needed there; the client IP is passed through directly.

When the request reaches HAProxy, we need to add the client IP to

XFF

and set

X-Real-IP

to the client IP. The relevant HAProxy configuration is:

<code>defaults
                mode            http
                log             global
                option          httplog
                option          dontlognull
                option  http-server-close
                log 127.0.0.1 local3
                option forwardfor       except 127.0.0.0/8
                option          redispatch
                retries         3
                timeout http-request    10s
                timeout queue           1m
                timeout connect         10s
                timeout client          5m
                timeout server          5m
                timeout http-keep-alive 10s
                timeout check           10s
                unique-id-format %{+X}o %ci%cp%fi%fp%Ts%rt%pid

frontend https_link_ha
        bind *:443 ssl crt /usr/local/etc/haproxy/cert/crt/ ca-file /usr/local/etc/haproxy/cert/ca/ca.pem verify optional
        mode http
        log-format "%ID %ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"
        option accept-invalid-http-request

        http-request set-header x-request-id %[unique-id]
        http-request set-header x-request-time %[date()]
        http-request set-header X-Real-IP %[src]

        default_backend pre

backend pre 
    server 1 10.74.136.13:8080 check inter 1500 rise 3 fall 3 weight 3</code>

The two key directives are:

option forwardfor except 127.0.0.0/8

– adds the client’s IP to the

XFF

header.

http-request set-header X-Real-IP %[src]

– sets

X-Real-IP

to the client’s IP.

After HAProxy, the request reaches the Ingress layer, which also receives the client IP in

XFF

. By default, Nginx Ingress does not enable XFF forwarding, so we must configure it via its ConfigMap:

<code>use-forwarded-headers: 'true'
compute-full-forwarded-for: 'true'
</code>

Applying these settings causes Nginx Ingress to reload automatically without a manual restart, and the application logs will now contain the true client IP.

Note that in some scenarios, such as when a CDN sits in front of the SLB, the IP obtained via

XFF

may be the CDN’s source IP rather than the end‑user’s IP.

operationsNginxIPIngresshaproxyX-Forwarded-For
Ops Development Stories
Written by

Ops Development Stories

Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.