A load balancer forwards raw TCP without parsing the traffic — or passes an encrypted stream straight through — so there is no request in which to write a forwarded-address header. How does the PROXY protocol get the original client address to the backend, and what must be true on both ends for it to work?
answer
- nothing to write a header into
- a preamble before the first application byte
- once per connection, not per request
- v1 readable line, v2 binary signature
- both ends configured, or it breaks hard
basics
~20 sThe PROXY protocol prepends a short preamble to the forwarded TCP connection, before any application bytes, carrying the original source and destination addresses and ports. Both ends must be configured for it: an unaware backend reads that preamble as application data.
solid answer
~50 sWhen a proxy forwards a connection without touching the payload, there is nowhere to add a header — the bytes may not even be HTTP, and if TLS is passed through, the proxy cannot read or modify them at all. The PROXY protocol solves that out of band: immediately after the TCP connection to the backend is established, and *before* any application bytes, the proxy writes a short preamble stating the original source and destination addresses and ports. Version 1 is a single readable line such as `PROXY TCP4 203.0.113.7 10.0.0.9 56324 443`; version 2 is binary, starting with a fixed 12-byte signature, and can carry extra typed fields. It is sent once per connection, never per request. Both ends must agree: a backend not expecting it treats the preamble as the first bytes of a request, and a backend expecting it that receives none will reject the connection. And because the sender chooses the address it claims, the backend must accept the preamble only from its proxy.
code
bash · 4 lines# A listener that requires PROXY protocol v1 will accept this;
# one that does not expect it answers 400 Bad Request.
printf 'PROXY TCP4 203.0.113.7 10.0.0.9 56324 443\r\nGET / HTTP/1.1\r\nHost: app.example.com\r\nConnection: close\r\n\r\n' \
| nc 10.0.0.9 80go deeper
Know that a proxy which does not read the traffic cannot add a header to it, so the original client address has to be carried some other way for the backend to see it.
Explain that the PROXY protocol writes a short preamble ahead of the application bytes, once per connection, and that both the sender and the receiver must be configured for it or the connection fails outright.
Show the operational reality: the mismatch symptoms on each side, health checks breaking because probes bypass the proxy, and the rule that the preamble is only trustworthy on a listener reachable exclusively by your proxy.
Own the choice between preserving the client address in-band, at the network layer, or not at all — and what each option costs in routing control, coupling between tiers, and the risk of a forgeable identity that other controls depend on.
## The problem Forwarded-address headers only exist because something in the path parses the request and can add a field to it. Two common arrangements remove that possibility: - The proxy is deliberately connection-level: it moves bytes between two sockets without interpreting them, which is what makes it cheap and protocol-agnostic. - The proxy passes an encrypted stream through untouched, so the payload is not merely unparsed but unreadable and unmodifiable. In both cases the backend's socket peer is the proxy, and there is no in-band place to say who the real client was. Anything derived from the client address at the backend — allowlists, per-client limits, geolocation, abuse attribution, plain access logs — is broken. ## What the PROXY protocol is The PROXY protocol, defined and popularised by HAProxy and now widely implemented, is a tiny out-of-band preamble. As soon as the proxy has opened its TCP connection to the backend, and **before it forwards a single application byte**, it writes a block describing the connection it received: the address family, the original source address and port, and the destination address and port the client had aimed at. The backend, if configured to expect it, consumes that block, records those values as the connection's real endpoints, and then hands the remaining stream to the application as usual. Everything the application reads afterwards is unmodified — which is the property that makes this work with TLS passthrough and with protocols that are not HTTP at all. ## v1 and v2 **Version 1** is human-readable and easy to reason about: one ASCII line terminated by CRLF. ``` PROXY TCP4 203.0.113.7 10.0.0.9 56324 443\r\n ``` **Version 2** is binary. It begins with a fixed 12-byte signature that cannot plausibly appear at the start of a normal request, carries a declared length, and supports typed optional fields — which is how implementations pass extra facts such as TLS details or an ALPN value alongside the addresses. v2 is the default in most modern deployments; v1 remains useful because you can read it in a packet capture at a glance. ## Per connection, not per request The single most common misunderstanding is to think of this as a header. It is not: it appears **once**, at the very start of the connection, ahead of everything. If many HTTP requests are then multiplexed or pipelined over that connection, they all inherit the one declared client address. That is fine for a connection the proxy opened for one client, and it is why a pool of connections shared between clients cannot use this mechanism meaningfully. ## Both ends must agree — the two failure modes There is no negotiation, so a mismatch is not graceful: - **Sender on, receiver off.** The backend reads `PROXY TCP4 ...` or the binary signature as the first bytes of a request. An HTTP server answers `400 Bad Request` or closes; a TLS listener sees garbage instead of a ClientHello and aborts the handshake. - **Receiver on, sender off.** The backend waits for a preamble and instead gets a real request or a ClientHello, fails to parse it, and drops the connection. The second case produces a classic operational trap: the proxy is configured correctly and traffic works, but *health checks and monitoring probes* that connect directly do not speak the preamble, so the backend rejects them and the pool is marked down. Any checker or debugging tool must go through the proxy, or speak the preamble itself, or hit a separate listener that does not require it. ```bash # Speaking PROXY protocol v1 by hand against a listener configured to require it printf 'PROXY TCP4 203.0.113.7 10.0.0.9 56324 443\r\nGET / HTTP/1.1\r\nHost: app.example.com\r\nConnection: close\r\n\r\n' | nc 10.0.0.9 80 ``` ## The security rule The preamble is a claim by whoever opened the connection, exactly like a forwarded header. If a listener accepts it from anywhere, any party who can reach that port can declare any source address they like and inherit whatever that address is trusted for — allowlists, rate-limit exemptions, clean audit records. So: enable it only on listeners reachable solely by your proxy, restrict accepted senders to the proxy's addresses where the implementation supports it, and never enable it on a port exposed to the internet. Implementations commonly offer an optional mode that accepts a connection with or without the preamble; that convenience is also a hole, because a direct client can simply choose to send one. ## Where it sits among the alternatives If the proxy already parses the requests, a forwarded header is simpler and needs no coordination at the transport layer. If you control routing end to end, some designs preserve the original source address at the network layer instead, so the backend sees the client directly and needs no preamble at all. The PROXY protocol is the answer for the middle case that is extremely common in practice: a proxy that must not, or cannot, look inside the stream, in front of backends that still need to know who called.
- Why can the backend not simply read the client address from the TCP connection itself?Because the proxy opened that connection; the kernel correctly reports the proxy as the peer. The only ways around it are to carry the original address in-band as data — a forwarded header when the proxy parses requests, or the PROXY protocol when it does not — or to arrange routing so the client's connection reaches the backend with its source address preserved, which requires control of the network path.
- After enabling the PROXY protocol, backends start being marked unhealthy even though real traffic works. What happened?The health checker connects directly and does not send the preamble, so a listener that requires one rejects the probe. Either point the check through the proxy, teach the checker to send the preamble, or expose a separate listener for checks that does not require it — and keep that listener unreachable from anywhere else.
- What is wrong with enabling the mode that accepts connections with or without the preamble?It removes the only thing making the claim trustworthy. A client connecting directly can choose to send a preamble naming any source address, inheriting allowlist entries, rate-limit exemptions and clean audit trails. Require the preamble, and restrict the listener to your proxy's addresses so nobody else can present one.
saying these in an interview costs you the question
- The backend can always see the real client address from the socket
- Just add X-Forwarded-For, it works for raw TCP too
- It is sent as a header on every request
- Enable it on the backend and it works no matter what the sender does
- It authenticates or encrypts the forwarded connection