proxy-protocol.md
July 5, 2026 · View on GitHub
When Ghostunnel terminates TLS, the backend only sees a plaintext connection from Ghostunnel itself — it does not know who the original client was, what TLS version was negotiated, or whether a client certificate was presented. The PROXY protocol fixes this: Ghostunnel prepends a binary header to each forwarded connection with the original client metadata. Backends can then log, enforce access control, or audit based on client identity without their own TLS stack.
Enabling
See [Command-Line Flags]({{< ref "flags.md" >}}) for the full flag reference.
Pass --proxy-protocol in server mode to enable PROXY protocol v2 with
connection info (source/destination IP and port):
ghostunnel server \
--listen=:8443 \
--target=localhost:8080 \
--keystore=server.p12 \
--cacert=ca.crt \
--allow-ou=my-service \
--proxy-protocol
To also include TLS metadata and/or client certificate details, use the
--proxy-protocol-mode flag (available since v1.10.0):
| Mode | What is sent |
|---|---|
conn | Connection info only (src/dst IP+port). Same as bare --proxy-protocol. |
tls | Connection info + TLS metadata (version, ALPN, SNI). No client cert details. |
tls-full | Connection info + TLS metadata + full client certificate details. |
# TLS metadata without client cert:
ghostunnel server ... --proxy-protocol-mode=tls
# Everything, including client cert:
ghostunnel server ... --proxy-protocol-mode=tls-full
--proxy-protocol-mode and --proxy-protocol are mutually exclusive; use one
or the other, not both.
The backend will receive a PROXY protocol v2 binary header on each new connection, followed by the normal application data stream.
What Ghostunnel Sends
Ghostunnel sends a version 2 (binary format) header with the PROXY
command. The address family is detected from the incoming connection: IPv4 or
IPv6 for TCP listeners, and UNIX_STREAM (with socket paths instead of
IP/port pairs) when listening on a UNIX domain socket.
Address Fields (All Modes)
| Field | Value |
|---|---|
| Source address/port | Original client IP and port |
| Destination address/port | Local address of the accepted connection (the listener IP and port the client connected to) |
Note that with a wildcard listen address (e.g. --listen :8443), the
destination IP is the specific interface address the client connected to,
not the wildcard.
TLV Extensions (tls and tls-full Modes)
When using --proxy-protocol-mode=tls or --proxy-protocol-mode=tls-full,
Ghostunnel includes TLV (Type-Length-Value) extensions with TLS connection
metadata:
| TLV | Type | Description |
|---|---|---|
PP2_TYPE_SSL | 0x20 | Container for SSL/TLS metadata (see below) |
PP2_TYPE_AUTHORITY | 0x02 | SNI hostname the client requested (if set) |
PP2_TYPE_ALPN | 0x01 | Negotiated ALPN protocol, e.g. h2 (if set) |
SSL Sub-TLVs
The PP2_TYPE_SSL TLV contains a 5-byte sub-header followed by nested
sub-TLVs:
Sub-header:
| Field | Size | Description |
|---|---|---|
| Client flags | 1 byte | Bitfield: 0x01 = SSL used, 0x02 = client cert on connection, 0x04 = client cert on session |
| Verify result | 4 bytes | 0 = certificate verified successfully |
Ghostunnel always sets the 0x01 (SSL used) flag. In tls-full mode, if the
client presented a certificate, the 0x02 and 0x04 flags are both set
(Ghostunnel does not distinguish between certificates presented on the
connection vs. the session). In tls mode the certificate flags are never
set, even if a client certificate was presented. The verify result is always
0, since Ghostunnel only forwards connections whose certificates passed
verification.
Nested sub-TLVs (always present in tls and tls-full modes):
| Sub-TLV | Type | Example value |
|---|---|---|
PP2_SUBTYPE_SSL_VERSION | 0x21 | TLS 1.3 |
Nested sub-TLVs (tls-full mode only, when a client certificate was provided):
| Sub-TLV | Type | Description |
|---|---|---|
PP2_SUBTYPE_SSL_CN | 0x22 | Client certificate Common Name (omitted if the certificate has no CN) |
PP2_SUBTYPE_SSL_CLIENT_CERT | 0x28 | Full client certificate in DER (ASN.1) encoding |
The tls-full mode is useful when backends need to perform their own access
control or auditing based on client certificate identity. See [Access Control
Flags]({{< ref "access-flags.md" >}}) for how Ghostunnel itself verifies
client certificates before forwarding.
Note: PP2_SUBTYPE_SSL_CLIENT_CERT (0x28) is not part of the original
HAProxy spec but is supported by the
go-proxyproto library and others.
The spec requires receivers to ignore unknown TLV types, so this is safe.
Backend Requirements
Your backend must be configured to expect PROXY protocol headers. It needs to parse the binary header before reading application data. Most servers and frameworks support this:
- nginx:
proxy_protocolparameter onlistendirective - Apache:
mod_remoteipwithRemoteIPProxyProtocol - HAProxy:
accept-proxyonbindlines - Custom apps: use a PROXY protocol parsing library for your language
Backends that do not expect PROXY protocol will see the binary header as garbage and reject the connection.
References
- PROXY protocol specification (HAProxy, covers v1 and v2; see section 2.2 for the TLV type registry)
- go-proxyproto (Go library used by Ghostunnel)