Client IP preservation

Enable the PROXY protocol

When the load balancer proxies a TCP connection, it overwrites the client’s source IP address with its own when communicating with the backend server. The PROXY protocol adds a header to a TCP connection to preserve the client’s IP address. This method solves the lost-client-IP problem for any application-layer protocol that transmits its messages over TCP/IP. To work, both the sender and receiver must support the protocol and have it enabled.

The load balancer adds the header to TCP connections before relaying them to upstream servers. When placed behind another proxy, it can also receive the PROXY protocol header attached to the incoming connection. This feature supports IPv4 and IPv6 addresses.

Receive the PROXY protocol Jump to heading

To accept a PROXY protocol header on incoming TCP connections:

  1. Add an accept-proxy argument to the bind line in a frontend section. This argument detects both PROXY protocol version 1 (text format) and PROXY protocol version 2 (binary format).

    The example below accepts the PROXY protocol header from incoming connections:

    haproxy
    frontend mywebsite
    bind :80 accept-proxy
    default_backend webservers
    haproxy
    frontend mywebsite
    bind :80 accept-proxy
    default_backend webservers

Send the PROXY protocol Jump to heading

To send a PROXY protocol version 1 header (text format) to the backend servers:

  • Add a send-proxy argument to the server lines in a backend section:

    haproxy
    backend webservers
    balance roundrobin
    server s1 192.168.56.20:3000 check send-proxy
    server s2 192.168.56.21:3000 check send-proxy
    haproxy
    backend webservers
    balance roundrobin
    server s1 192.168.56.20:3000 check send-proxy
    server s2 192.168.56.21:3000 check send-proxy

To send a PROXY protocol version 2 header (binary format) to the backend servers:

  • Add a send-proxy-v2 argument to the server lines in a backend section:

    haproxy
    backend webservers
    balance roundrobin
    server s1 192.168.56.20:3000 check send-proxy-v2
    server s2 192.168.56.21:3000 check send-proxy-v2
    haproxy
    backend webservers
    balance roundrobin
    server s1 192.168.56.20:3000 check send-proxy-v2
    server s2 192.168.56.21:3000 check send-proxy-v2

PROXY protocol on AWS Network Load Balancers Jump to heading

AWS NLB can use the PROXY protocol to set the IP address in requests. Enable this feature as described here:

Then add the keyword accept-proxy to your bind line, as described in the section Receive the PROXY protocol. The requests will then automatically have the right source IP address.

Send metadata in TLV format Jump to heading

This section applies to:

  • HAProxy 2.9 and newer
  • HAProxy ALOHA 16.5 and newer
  • HAProxy Enterprise 2.9r1 and newer

While the PROXY protocol’s primary function is to preserve a client’s source IP address when traffic traverses one or more reverse proxies, it can also serve as a way to attach metadata to TCP connections. In the PROXY Protocol v2 (binary header format), Type-Length-Value (TLV) vectors store variable-length data in the PROXY protocol header, allowing you to transmit information relevant to the sender and receiver.

Here are a few use cases for TLV vectors:

  • Azure’s Private Link platform enables you to connect your on-premises network to Azure services without exposing traffic to the Internet. With HAProxy running on-premises, you can receive messages over the Private Link while preserving the client’s IP address, as Azure supports the PROXY protocol header. Azure will also send a TLV vector that contains additional connection information, such as the LinkID of the private endpoint, helping to uniquely identify consumers.
  • AWS Network Load Balancer can serve as a layer 4 load balancer that distributes traffic to multiple HAProxy instances running in the cloud or on-premises. Enable the PROXY protocol in AWS Network Load Balancer to preserve the client’s IP address as it passes through Network Load Balancer. In addition, when traffic to the Network Load Balancer comes through a VPC endpoint service, AWS Network Load Balancer adds a TLV vector that contains the VPC endpoint ID.

TLV format Jump to heading

TLV vectors have the following format:

  • The first byte is the type of vector.
  • The second two bytes represent the length in bytes of the value (not including the type and length bytes).
  • The last part is a variable number of bytes, the length specified by the length field, containing the value.

The PROXY protocol defines several types, which you can learn more about in the PROXY protocol specification:

#define PP2_TYPE_ALPN 0x01
#define PP2_TYPE_AUTHORITY 0x02
#define PP2_TYPE_CRC32C 0x03
#define PP2_TYPE_NOOP 0x04
#define PP2_TYPE_UNIQUE_ID 0x05
#define PP2_TYPE_SSL 0x20
#define PP2_SUBTYPE_SSL_VERSION 0x21
#define PP2_SUBTYPE_SSL_CN 0x22
#define PP2_SUBTYPE_SSL_CIPHER 0x23
#define PP2_SUBTYPE_SSL_SIG_ALG 0x24
#define PP2_SUBTYPE_SSL_KEY_ALG 0x25
#define PP2_TYPE_NETNS 0x30
#define PP2_TYPE_ALPN 0x01
#define PP2_TYPE_AUTHORITY 0x02
#define PP2_TYPE_CRC32C 0x03
#define PP2_TYPE_NOOP 0x04
#define PP2_TYPE_UNIQUE_ID 0x05
#define PP2_TYPE_SSL 0x20
#define PP2_SUBTYPE_SSL_VERSION 0x21
#define PP2_SUBTYPE_SSL_CN 0x22
#define PP2_SUBTYPE_SSL_CIPHER 0x23
#define PP2_SUBTYPE_SSL_SIG_ALG 0x24
#define PP2_SUBTYPE_SSL_KEY_ALG 0x25
#define PP2_TYPE_NETNS 0x30

In addition, 16 type values are reserved for application-specific data, such as those used by Azure and AWS.

#define PP2_TYPE_MIN_CUSTOM 0xE0
#define PP2_TYPE_MAX_CUSTOM 0xEF
#define PP2_TYPE_MIN_CUSTOM 0xE0
#define PP2_TYPE_MAX_CUSTOM 0xEF

A range of 8 type values are reserved for temporary, experimental use by application developers and protocol designers. You shouldn’t use these in production:

#define PP2_TYPE_MIN_EXPERIMENT 0xF0
#define PP2_TYPE_MAX_EXPERIMENT 0xF7
#define PP2_TYPE_MIN_EXPERIMENT 0xF0
#define PP2_TYPE_MAX_EXPERIMENT 0xF7

Receive TLV fields Jump to heading

Use the fc_pp_tlv fetch method to receive TLV vectors from a PROXY protocol header. The fetch returns a string type.

Azure example:

haproxy
frontend fe_main
bind :80 accept-proxy
tcp-request content set-var(sess.azure_vpcid) fc_pp_tlv(0xEE),bytes(1) if { fc_pp_tlv(0xEE),bytes(0,1),hex eq 01 }
haproxy
frontend fe_main
bind :80 accept-proxy
tcp-request content set-var(sess.azure_vpcid) fc_pp_tlv(0xEE),bytes(1) if { fc_pp_tlv(0xEE),bytes(0,1),hex eq 01 }

AWS example:

haproxy
frontend fe_main
bind :80 accept-proxy
tcp-request content set-var(sess.aws_linkid) fc_pp_tlv(0xEA),bytes(1) if { fc_pp_tlv(0xEA),bytes(0,1),hex eq 01 }
haproxy
frontend fe_main
bind :80 accept-proxy
tcp-request content set-var(sess.aws_linkid) fc_pp_tlv(0xEA),bytes(1) if { fc_pp_tlv(0xEA),bytes(0,1),hex eq 01 }

Send TLV fields Jump to heading

Add the set-proxy-v2-tlv-fmt argument to server directives to send TLV fields to a backend server. This argument must be accompanied by the send-proxy-v2 argument.

Azure example:

haproxy
backend webapp
server srv1 192.168.1.10:80 send-proxy-v2 set-proxy-v2-tlv-fmt(0xEE) %[fc_pp_tlv(0xEE)]
haproxy
backend webapp
server srv1 192.168.1.10:80 send-proxy-v2 set-proxy-v2-tlv-fmt(0xEE) %[fc_pp_tlv(0xEE)]

AWS example:

haproxy
backend webapp
server srv1 192.168.1.10:80 send-proxy-v2 set-proxy-v2-tlv-fmt(0xEA) %[fc_pp_tlv(0xEA)]
haproxy
backend webapp
server srv1 192.168.1.10:80 send-proxy-v2 set-proxy-v2-tlv-fmt(0xEA) %[fc_pp_tlv(0xEA)]

See also Jump to heading

Do you have any suggestions on how we can improve the content of this page?