Skip to main content
Version: Dev

Client IP Resolution

The AISIX gateway attributes every request to a client address. When callers connect directly, that address is the TCP peer of the connection. When the gateway runs behind a load balancer or an ingress, the TCP peer is the device in front, and the gateway needs that device to tell it who the original client was. This page explains the two ways the gateway accepts that information, X-Forwarded-For and the PROXY protocol, and how to configure which devices it trusts to supply it.

Where the Client Address Is Used​

The resolved client address is the one the gateway uses everywhere it needs to know who the caller is:

  • The client_source_ip field of usage events and the client.address attribute of OTLP traces.
  • A model's allowed_cidrs, passthrough route source allowlists, and MCP source_cidrs.
  • Client-IP hash routing.

The access log also records the connection's peer, the address and source port of the connection itself.

With the default configuration, the gateway trusts no device in front of it, and the client address is always the TCP peer.

Trust the Devices in Front of the Gateway​

Both mechanisms are gated by one startup setting, proxy.real_ip.trusted_proxies, a list of CIDRs or bare IP addresses. A device is trusted when the address the gateway sees as the TCP peer falls inside one of the entries. The list is empty by default.

proxy:
real_ip:
trusted_proxies:
- "10.0.0.0/8"
- "127.0.0.1/32"

In an environment-only deployment, set the list comma-separated:

AISIX_PROXY__REAL_IP__TRUSTED_PROXIES=10.0.0.0/8,127.0.0.1/32

List only the addresses of the devices that actually forward traffic to the gateway. A trusted peer can name any client address it likes, so an entry that covers callers lets those callers choose the address the gateway attributes them to.

Behind an HTTP Proxy or Ingress​

An HTTP reverse proxy or ingress controller usually appends the address it received the request from to the X-Forwarded-For request header. When the TCP peer is in trusted_proxies, the gateway reads that header to find the client. A request from any other peer is attributed to the peer, and its header is ignored.

Two more settings under proxy.real_ip control how the header is read:

SettingDefaultEffect
headerx-forwarded-forThe request header to read.
recursivefalseWhen false, the client is the rightmost address in the header. When true, the gateway walks the header from right to left, skips every address inside trusted_proxies, and takes the first address that is not trusted. Use true when several trusted proxies are chained in front of the gateway.

If the header is absent or contains no usable address, the client is the TCP peer.

Behind a Layer-4 Load Balancer​

A layer-4 (TCP) load balancer that rewrites the connection's source address cannot add an HTTP header, because it does not parse HTTP. Instead, it can open each connection to the gateway with a PROXY protocol header that names the original client. The gateway reads PROXY protocol version 1 and version 2 headers.

Detect the Header​

Every proxy listener checks the first bytes of each new connection for a PROXY protocol header, before the TLS handshake and before HTTP. This applies to the listener set by proxy.addr and proxy.tls and to every entry of proxy.listeners, with or without TLS. There is no switch to turn it on and no other configuration key. The admin, metrics/status, and diagnostics listeners never read a PROXY header.

What happens next depends on the connection:

ConnectionResult
No PROXY headerServed exactly as before. Platform probes and other callers that reach the same port directly keep working.
A valid header from a peer inside trusted_proxiesThe header's source address and port replace the TCP peer for the rest of the connection.
A header from a peer outside trusted_proxiesThe connection is closed.
A malformed or truncated header from a trusted peerThe connection is closed.

A version 2 LOCAL header, which a load balancer uses for its own health checks, a version 2 header whose transport or address family is unspecified or whose family is UNIX sockets, and a version 1 UNKNOWN header carry no client address. The gateway reads past them and keeps the TCP peer. Type-length-value extensions in a version 2 header are ignored.

When the header is honoured, its source address is the connection's peer in the access log, and it is the starting point for every use of the client address listed in Where the Client Address Is Used.

Enable the PROXY Protocol​

  1. Find the address the gateway sees as the TCP peer of a connection from the load balancer. This is usually the address range of the load balancer itself. On Kubernetes, it can be a node address instead. See Keep the Load Balancer Address on Kubernetes.

  2. Add that range to proxy.real_ip.trusted_proxies and roll it out to every gateway:

    proxy:
    real_ip:
    trusted_proxies:
    - "10.0.0.0/16" # Address range of the load balancer.
  3. Turn on PROXY protocol on the target group or backend of the load balancer.

Roll out the gateway configuration first. A gateway that does not trust the load balancer closes every connection that carries a PROXY header, so turning on the header before the gateway trusts its sender rejects all traffic from that load balancer.

A trusted address is also trusted to supply X-Forwarded-For on a connection that carries no PROXY header. After you turn on the header, every connection from the load balancer carries one, so this only matters for other traffic that reaches the gateway from inside the trusted range.

Configure It with the Helm Chart​

With the api7/aisix Helm chart, set the same key under config:

config:
proxy:
real_ip:
trusted_proxies:
- "10.0.0.0/16"

See Set Any Other Gateway Configuration for how the chart renders config.

Keep the Load Balancer Address on Kubernetes​

When the load balancer reaches the gateway through a Kubernetes Service with externalTrafficPolicy: Cluster, which is the Kubernetes default, kube-proxy rewrites the connection's source address to a node address so that it can forward the connection to a pod on any node. The TCP peer the gateway sees is then a node address, not the address of the load balancer.

In that case, trusted_proxies must cover the node network. Otherwise, every connection that carries a PROXY header is closed.

With externalTrafficPolicy: Local, kube-proxy delivers the connection only to a pod on the node that received it and keeps its source address, so the TCP peer is the address of the load balancer. The chart sets the policy through service.externalTrafficPolicy.

Combine It with X-Forwarded-For​

X-Forwarded-For handling still applies to a connection that carries a PROXY header. The address from the PROXY header is the peer that the gateway checks against trusted_proxies before it reads X-Forwarded-For. A client behind the load balancer whose own address is not trusted therefore cannot have the gateway believe an X-Forwarded-For header it sends.

If an HTTP proxy that sets X-Forwarded-For sits behind the layer-4 load balancer, add both the range of the load balancer and the range of that proxy to trusted_proxies.

Diagnose Closed Connections​

The gateway logs a WARN line for each connection it closes because of its PROXY header. Both lines carry the listener label and the TCP peer the connection came from:

MessageCause
PROXY protocol header from a peer outside proxy.real_ip.trusted_proxies; closing the connectionThe TCP peer is not inside trusted_proxies. Add the address in peer to the list if it is your load balancer or, on Kubernetes, one of your nodes.
malformed PROXY protocol header; closing the connectionThe header from a trusted peer could not be parsed. The reason field says why, such as a header that is truncated or longer than the gateway accepts.

A connection whose first bytes are PROXY followed by a space is always read as a version 1 PROXY header. A request that uses a custom HTTP method named PROXY cannot be served on the proxy listeners.

Dual-Stack Listeners​

On a proxy listener bound to [::], the operating system reports an IPv4 client as an IPv4-mapped IPv6 address such as ::ffff:192.0.2.10. The gateway converts it back to the plain IPv4 form, here 192.0.2.10, where it accepts the connection, and does the same for an address from a PROXY header. IPv4 clients are therefore reported in usage events, traces, and the access log as plain IPv4 addresses, and IPv4 entries in trusted_proxies, allowed_cidrs, passthrough source allowlists, MCP source_cidrs, and client-IP hash routing match them.

Write IPv4 ranges in these lists in IPv4 form. An entry written in the ::ffff: form, such as ::ffff:10.0.0.0/104, does not match an IPv4 client.