End-to-End Client Real IP Passthrough in OpenResty Edge
When a front proxy, CDN, or Layer 4 load balancer sits in front of your gateway, the address the gateway receives becomes the proxy server’s IP—rendering IP-based rate limiting, access control, and audit logging ineffective. This guide covers all four real IP recovery methods supported by OpenResty Edge: Proxy Protocol, X-Forwarded-For, X-Real-IP, and custom headers. For each method, it walks through applicable scenarios, configuration steps, and security considerations. It also explains how to forward the recovered real client IP to your backend origin servers on upstream requests, closing the full inbound-to-outbound pipeline. You can select the right approach for your proxy topology and complete end-to-end configuration in OpenResty Edge.
1. Use Cases and Topology
When requests are forwarded through a third-party proxy, the client address received by OpenResty Edge is the proxy server’s address—not the actual end-user IP.
Real Client (104.28.243.40)
│
▼
Front Proxy Server (52.53.251.226)
│ Passes real IP (via XFF / Real-IP / Proxy Protocol)
▼
OpenResty Edge Gateway
│ Resolves client-addr → used for access control / rate limiting / logging
▼
Backend Service / Origin Server
Typical scenarios where recovering the real client IP is necessary:
- Conditional logic: Geolocation matching and IP allowlist/blocklist enforcement based on the user’s actual IP
- Rate limiting: Throttling individual users by their real IP rather than the proxy IP
- Audit logging: Recording the true source address in access logs
OpenResty Edge supports four IP passthrough methods, each suited for different proxy topologies and protocol layers.
2. IP Passthrough Methods Overview
| Method | Transport Layer | Stream Support | HTTP Support | Multi-hop Proxy |
|---|---|---|---|---|
| proxy protocol | TCP/UDP/TLS connection preamble | ✅ | ✅ | ✅ |
| X-Forwarded-For | HTTP header | ❌ | ✅ | ✅ |
| X-Real-IP | HTTP header | ❌ | ✅ | ❌ |
| Custom header | HTTP header | ❌ | ✅ | Depends on implementation |
Stream scenario constraints: TCP/UDP Layer 4 proxies operate below the HTTP layer and have no HTTP headers available, making all header-based methods inapplicable. Among the methods covered in this guide, proxy protocol is the de facto choice for Stream scenarios. TOA (TCP Option Address)—which encodes the client IP in TCP option fields—is another Layer 4 option, but it requires kernel module support. Its compatibility characteristics and deployment overhead differ from those of proxy protocol, so the right choice depends on your specific deployment environment.
2.1 proxy protocol V1
Core mechanism: When establishing a TCP connection, the front proxy prepends a standardized PROXY header line to the data stream (Proxy Protocol v1 format shown below):
PROXY TCP4 <real client IP> <proxy server IP> <source port> <destination port>\r\n
The gateway parses this header at the TCP layer, extracts the real IP, writes it to proxy_protocol_addr, and updates client-addr accordingly.
Applicable scenarios: Layer 4 TCP/UDP Stream proxying; HTTP scenarios where the upstream is configured to send Proxy Protocol headers.
Limitations:
- The front proxy must send a valid, compliant Proxy Protocol v1 header
- The target port on OpenResty Edge must have Proxy Protocol support enabled beforehand (see Section 3.1)
- Front proxy server IPs must be added to the trusted IP list
- OpenResty Edge supports multiple IP passthrough methods concurrently on the same port
Configuration steps: Enable at the port level first (Section 3.1), then select Proxy Protocol as the source in global configuration (Section 3.2).
2.2 Real-IP
Core mechanism: The front proxy sets the X-Real-IP HTTP header to the real client IP; the gateway reads this header and updates client-addr.
Applicable scenarios: Scenarios where the upstream proxy is solely responsible for setting this header to the end user’s real IP, and you do not need to reconstruct the full proxy chain at the gateway. Suitable as long as the upstream is controlled and the write logic is well-defined—not limited to single-hop topologies.
Limitations:
X-Real-IPhas no formal standard, and behavior varies across upstream implementations. Most write a single IP value; others (e.g., those treating it as anX-Forwarded-Foralias) may write multiple comma-separated values. OpenResty Edge’s parsing behavior depends on your configuration—confirm the upstream write format before use.- This header has no per-hop append semantics; intermediate proxy IPs are not recorded automatically. If you need to trace the full multi-hop proxy chain, use
X-Forwarded-Forinstead. - Applicable to HTTP/HTTPS only.
2.3 X-Forwarded-For
Core mechanism: At each hop, the proxy appends the inbound IP to the X-Forwarded-For header, building a comma-separated list of IPs as the request traverses the proxy chain.
Without recursive search enabled: OpenResty Edge selects the rightmost (most recently appended) IP in the XFF list as the client address.
With recursive IP search enabled: The system traverses the XFF list from right to left, skipping all addresses in the trusted source list, and uses the first non-trusted address as the real client IP—effectively protecting against header spoofing attacks.
Applicable scenarios: Multi-hop HTTP proxy chains; compatible with mainstream proxies (Nginx, HAProxy, CDN nodes).
Limitations: Headers can be forged by clients; always configure trusted source IPs strictly (see Section 3.3).
2.4 Custom Header
Core mechanism: You specify any HTTP header field as the real IP source; OpenResty Edge reads the client IP from that field.
Applicable scenarios: Internal systems with an existing private IP passthrough convention (e.g., CF-Connecting-IP, True-Client-IP); cases where you want to preserve the upstream proxy’s existing header field without modification.
Limitations: The upstream proxy must be configured to write the designated header field; applicable to HTTP/HTTPS only.
3. OpenResty Edge Configuration
3.1 Enabling Proxy Protocol Support on a Gateway Partition Port
When using the Proxy Protocol method, you must first enable Proxy Protocol support at the port level; all other methods can skip this step.
- Open the Admin Web console and navigate to Gateway Cluster > Gateway Partition
- Select the target partition and click Edit
- Locate the target port in the port list and click Edit
- Check Enable proxy protocol and save
- In the application configuration, select the partition(s) you want to push the configuration to
Reserved ports — do not use the following ports in application configurations: 80, 443 (default proxy, public-facing); 11212 (shared SSL session, internal); 8090 (cluster resource sharing, internal); 8091 (node status query, loopback only).
3.2 Enabling the Protocol in Global Config
- Go to the Global Config page
- Under Client IP Source, select the method in use:
| Option | Description |
|---|---|
| X-Forwarded-For | Read the real client IP and port from the X-Forwarded-For HTTP header |
| Proxy Protocol | Read from the Proxy Protocol header (port-level setup in Section 3.1 required) |
| X-Real-IP | Read the real client IP and port from the X-Real-IP HTTP header |
| Custom Header | Read from a custom HTTP header field; also specify the header field name |
- (Optional) Enable Recursive IP Search: When the header contains multiple IPs, the gateway searches right to left and selects the first non-trusted address as the true source IP—well suited for multi-hop proxy deployments.
- After saving, click Publish. The configuration is pushed to all gateway servers with no reload or restart required.
3.3 Configuring Trusted Source IPs
Trusted hosts to set real IP defines which upstream addresses are permitted to set the real IP. OpenResty Edge only accepts the IP from a Real IP header and updates client-addr if the connecting peer IP appears in this list; headers arriving from untrusted addresses are ignored.
- On the Global Config page, locate Trusted hosts to set real IP
- Enter the IP address of your front proxy server (e.g.,
52.53.251.226); multiple entries are supported - Save and publish
Behavior comparison:
| Request Source | Header | client-addr Result |
|---|---|---|
52.53.251.226 (trusted) | X-Forwarded-For: 104.28.243.40 | 104.28.243.40 |
| Untrusted host | X-Forwarded-For: 104.28.243.40 | The untrusted host’s actual IP |
Note: Once real source IP configuration is active, it affects all client-IP-dependent features, including Client City, Client Address display, and the Limit Request Rate action. Exception: TLS/SSL handshake rate limiting is not affected—handshakes occur before HTTP-layer parsing, at which point client-addr has not yet been resolved.
3.4 Multi-Protocol Support on the Same Port (Comparison with Native Nginx)
Native Nginx behavior: In Nginx, proxy_protocol is a port-level toggle on the listen directive:
# Nginx: only one option per port
listen 80 proxy_protocol; # This port only accepts Proxy Protocol connections
listen 80; # This port only accepts plain connections
If the upstream sends a mix of connections—some with Proxy Protocol headers, some without—you must split them across separate listening ports, adding complexity to port management and firewall rules.
OpenResty Edge’s approach: Once port-level Proxy Protocol support is enabled, the gateway automatically detects whether an incoming connection begins with a valid PROXY line and routes it to the appropriate parsing path. A single port can handle both connection types simultaneously:
- Connection starts with
PROXY TCP4 ...→ parse the Proxy Protocol header and extract the real IP - Connection is plain HTTP/TCP → process normally, no protocol header required
This is especially useful during migrations when old and new proxies are running concurrently—your operations team no longer needs to maintain separate listening ports for different upstreams.
4. Key Variables
4.1 client-addr
client-addr is the authoritative client address in OpenResty Edge, used throughout all client IP-related processing logic.
Value resolution logic:
- The initial value is the TCP peer IP (i.e., the front proxy’s address)
- If this IP appears in the trusted source list and the request carries a valid Real IP header (or Proxy Protocol header),
client-addris updated to the parsed real IP - If the peer IP is not in the trusted list,
client-addrremains set to the TCP connection source IP
Scope of impact:
- Conditional evaluation in page rules (variables such as Client City, Client Address)
- The
Limit Request Rateaction (rate limiting by real user IP) - The client address field in access logs
- All EdgeLang rules that reference
client-addr
Not affected: TLS/SSL handshake rate limiting. Handshakes occur before HTTP-layer parsing, so client-addr has not yet been resolved at that stage and still reflects the TCP connection source IP.
4.2 proxy_protocol_addr
proxy_protocol_addr is the client IP extracted directly from the Proxy Protocol header, not subject to trusted source filtering.
| Variable | Data Source | Subject to Trust Rules | Typical Use |
|---|---|---|---|
proxy_protocol_addr | Raw value from the Proxy Protocol header | No | Auditing, debugging |
client-addr | Resolved value after trust rule evaluation | Yes | Business logic, rate limiting, logging |
When the source IP passes trust verification, both variables hold the same value. When the source IP is untrusted, proxy_protocol_addr still retains the raw value parsed from the protocol header, while client-addr is not updated and continues to reflect the TCP connection source IP. In audit scenarios requiring both the raw header value and the final resolved IP, reference each variable independently.
5. Page Rules
This section sets up an EdgeLang page rule that echoes the gateway’s recognized client-addr value directly in the response body, providing a straightforward way to verify that each IP passthrough method is working correctly.
Steps:
- Open the target application (e.g.,
test-edge.com) and select Page Rules - Click Edit (or create a new rule)
- Write the following EdgeLang rule:
# Condition: true (applies to all requests)
# Action: echo the current client-addr value in the response body
true =>
say(client-addr),
done;
- Save and publish. The configuration is pushed to all gateway servers with no restart required.
After publishing, every request to this domain will return the client address currently recognized by OpenResty Edge in the response body, allowing you to immediately verify whether IP passthrough is configured correctly.
6. Verification
The following tests assume the target domain is test-edge.com and the page rule from Section 5 has already been published.
6.1 curl with X-Forwarded-For
Prerequisites: Global Config has X-Forwarded-For selected as the source, and the trusted source IP list includes the address of the proxy server making the request.
Single IP scenario:
curl http://test-edge.com/ -H "X-Forwarded-For: 104.28.243.40"
# Expected output: 104.28.243.40
# The request comes from a trusted proxy; the IP in the XFF header is accepted and client-addr is updated to 104.28.243.40
Multiple IP scenario (rightmost IP is used):
curl http://test-edge.com/ -H "X-Forwarded-For: 104.28.243.40, 105.56.18.52"
# Expected output: 105.56.18.52
# By default, the rightmost IP is used; with recursive search enabled, the first non-trusted IP is used instead
Untrusted source verification:
# Request sent from a host not in the trusted list
curl http://test-edge.com/ -H "X-Forwarded-For: 104.28.243.40"
# Expected output: the requesting host's own egress IP (e.g., 203.0.113.5)
# The source is untrusted; the XFF header is ignored and client-addr falls back to the actual TCP connection source IP
6.2 curl with X-Real-IP
Prerequisites: Global Config has X-Real-IP selected as the source, and the request comes from a trusted proxy.
curl http://test-edge.com/ -H "X-Real-IP: 104.28.243.40"
# Expected output: 104.28.243.40
# The gateway reads the X-Real-IP header and updates client-addr accordingly
6.3 curl with a Custom HTTP Header
Prerequisites: Global Config has Custom Header selected as the source and the custom header name has been specified (e.g., X-My-Real-IP). The request comes from a trusted proxy.
curl http://test-edge.com/ -H "X-My-Real-IP: 104.28.243.40"
# Expected output: 104.28.243.40
# The gateway reads the value from the header field specified in configuration; the same trust rules apply as with other methods
6.4 curl with Proxy Protocol
Prerequisites: The target port has Proxy Protocol enabled in the gateway partition (see Section 3.1), and Global Config has Proxy Protocol selected as the source.
Standard curl does not send Proxy Protocol headers by default. You can verify using either of the following methods:
Method 1: Use the curl built-in flag (v7.60.0+)
curl http://test-edge.com/ --haproxy-protocol
# Expected output: local machine's egress IP
# curl automatically prepends the PROXY protocol header to the TCP stream; the gateway extracts the client IP from it
Method 2: Manually construct the header with nc to simulate a specific real IP
printf "PROXY TCP4 104.28.243.40 52.53.251.226 12345 80\r\nGET / HTTP/1.0\r\nHost: test-edge.com\r\n\r\n" \
| nc test-edge.com 80
# Expected output: 104.28.243.40
# The gateway parses the manually crafted PROXY header and sets both proxy_protocol_addr and client-addr to 104.28.243.40
7. Further Reading
OpenResty Edge supports forwarding the recovered real client IP to backend origin servers on upstream requests via custom request headers (such as X-Real-IP or X-Forwarded-For), allowing origin servers to read it directly without any network-layer changes.
We recommend the following two official articles for a deeper dive:
How to Pass the Real Client IP Address to Backend Servers via Special Request Headers Explains how to configure page rules to inject the client IP into custom request headers and forward them to backend services on upstream requests.
Restoring the Real Client IP Address Accurately in OpenResty Edge A complete walkthrough from configuring trusted source IP addresses to verifying client address resolution—complementing the content of this guide.
About OpenResty Edge
OpenResty Edge is a full-featured gateway platform designed for microservices and distributed traffic architectures, built by OpenResty Inc. It unifies traffic management, private CDN, API gateway, and security protection in a single product, enabling you to build, manage, and secure modern applications with ease. With industry-leading performance and scalability, OpenResty Edge is capable of handling the demands of high-concurrency and high-load environments. It supports traffic scheduling for containerized workloads including Kubernetes and can manage massive numbers of domains, making it well suited for large-scale websites and complex application deployments.
About The Author
Yichun Zhang (Github handle: agentzh), is the original creator of the OpenResty® open-source project and the CEO of OpenResty Inc..
Yichun is one of the earliest advocates and leaders of “open-source technology”. He worked at many internationally renowned tech companies, such as Cloudflare, Yahoo!. He is a pioneer of “edge computing”, “dynamic tracing” and “machine coding”, with over 22 years of programming and 16 years of open source experience. Yichun is well-known in the open-source space as the project leader of OpenResty®, adopted by more than 40 million global website domains.
OpenResty Inc., the enterprise software start-up founded by Yichun in 2017, has customers from some of the biggest companies in the world. Its flagship product, OpenResty XRay, is a non-invasive profiling and troubleshooting tool that significantly enhances and utilizes dynamic tracing technology. And its OpenResty Edge product is a powerful distributed traffic management and private CDN software product.
As an avid open-source contributor, Yichun has contributed more than a million lines of code to numerous open-source projects, including Linux kernel, Nginx, LuaJIT, GDB, SystemTap, LLVM, Perl, etc. He has also authored more than 60 open-source software libraries.


















