HAProxy PROXY protocol listeners
The HAProxy PROXY protocol lets a TCP proxy or load balancer pass the original client's address and port to a backend server. Without it, the backend sees the proxy as the other end of its connection. Mu Server supports PROXY protocol v1 and v2 in versions 2 (since 2.1.0), 3 and 4.
How it works
The proxy sends connection information once, at the start of the backend connection, before any HTTP or TLS traffic. Version 1 uses a text header; version 2 uses a binary header. Both carry source and destination connection details.
This is useful for TLS passthrough: a proxy can forward the encrypted connection and report the client's
address without decrypting HTTP requests. With HTTP headers such as Forwarded or
X-Forwarded-For, the proxy needs access to the HTTP request instead. PROXY protocol metadata
is available through Mu's connection API, separately from request headers.
Enable PROXY protocol
PROXY protocol is disabled by default. Enable it on the server builder, then read the forwarded information
from request.connection().proxyInfo().
Mu Server 2 and 3
Use withHAProxyProtocolEnabled(true):
import io.muserver.MuServer;
import io.muserver.MuServerBuilder;
import io.muserver.ProxiedConnectionInfo;
MuServer server = MuServerBuilder.httpsServer()
.withHAProxyProtocolEnabled(true)
.addHandler((request, response) -> {
ProxiedConnectionInfo info = request.connection().proxyInfo().orElseThrow();
response.write(String.valueOf(info.sourceAddress()));
return true;
}).start();
Mu Server 4
Use HAProxyProtocolConfigBuilder to enable PROXY protocol and configure the accepted versions,
preamble timeout and v2 payload limit:
import io.muserver.HAProxyProtocolConfigBuilder;
import io.muserver.MuServer;
import io.muserver.MuServerBuilder;
import io.muserver.ProxiedConnectionInfo;
MuServer server = MuServerBuilder.httpsServer()
.withHAProxyProtocolConfig(HAProxyProtocolConfigBuilder.config())
.addHandler((request, response) -> {
ProxiedConnectionInfo info = request.connection().proxyInfo().orElseThrow();
response.write(String.valueOf(info.sourceAddress()));
return true;
}).start();
The config builder defaults to both V1 and V2, a ten-second timeout and a 65,535-byte v2 payload limit. For example, to accept only V2 with a shorter timeout and smaller limit:
import io.muserver.HAProxyProtocolVersion;
import java.util.List;
import java.util.concurrent.TimeUnit;
// Use this configuration on the server builder:
.withHAProxyProtocolConfig(HAProxyProtocolConfigBuilder.config()
.withSupportedVersions(List.of(HAProxyProtocolVersion.V2))
.withTimeout(5, TimeUnit.SECONDS)
.withMaxV2PayloadSize(4096))
The timeout is the time allowed to receive the PROXY preamble after accepting a connection; HTTP idle
and request timeouts are separate. The v2 payload limit excludes the fixed 16-byte header and can be
from 0 to 65,535 bytes. Use withEnabled(false) on the config builder to disable PROXY protocol.
The boolean withHAProxyProtocolEnabled(...) API is deprecated in Mu4. When migrating,
replace withHAProxyProtocolEnabled(true) with the config builder above. Calling the boolean
method with true replaces any custom PROXY configuration with the defaults.
PROXY protocol works with both plaintext HTTP and HTTPS.
Configure the proxy and listener
Enable HAProxy's send-proxy (v1) or send-proxy-v2 (v2) on the backend server line.
Every connection to the enabled listener must send a PROXY header before TLS or HTTP, including health
checks. With TLS passthrough, the TLS handshake follows the PROXY header.
Only trusted proxies should be able to connect to this listener. Restrict access using the bind address, firewall, security group or network policy. PROXY metadata is supplied by the connecting peer and is not proof of the client's identity; allowing untrusted connections would let callers advertise false addresses.
Read connection metadata
HttpConnection.proxyInfo() returns an Optional<ProxiedConnectionInfo>.
It is empty when no proxy information is available. The example above expects it to be present because
the listener requires PROXY protocol. The ProxiedConnectionInfo API exposes:
sourceAddress()andsourcePort(): the client address and port reported by the proxy.destinationAddress()anddestinationPort(): the destination address and port reported by the proxy.
Some PROXY headers do not advertise endpoints, so an address can be null even when the
Optional contains metadata. The connection's remoteAddress() and
localAddress() continue to describe the actual socket endpoints.
PROXY information belongs to the entire connection. Keep-alive requests, HTTP/2 streams and a WebSocket upgrade on that connection share it. Configure your proxy so that it does not reuse a backend connection for unrelated client identities.
For the wire format and proxy interoperability details, see the HAProxy PROXY protocol specification.