A Go library implementation of the PROXY protocol, versions 1 and 2.
Go
625
269 commits
updated Sep 14, 2026
A Go library implementation of the HAProxy PROXY protocol specification 3.4. It covers protocol versions 1 (text) and 2 (binary), carrying the original client connection information across NAT and proxy layers.
Use it on either side of a PROXY-aware hop: send headers with Header.WriteTo
or Header.FormatUDPDatagram, and receive them with Listener, NewConn,
Read, or ParseUDPDatagram. The stream APIs are for TCP and Unix streams;
UDP uses packet helpers because the spec requires a header in every datagram.
$ go get github.com/pires/go-proxyproto
The fastest way to get started is the runnable programs under
examples/ and the API examples on
pkg.go.dev:
| Goal | Where to look |
|---|---|
| Minimal client | examples/client |
| Minimal server | examples/server |
| HTTP server | examples/httpserver |
| Server + client over TLS (PROXY header before TLS) | examples/tlsserver, examples/tlsclient |
| UDP receiver and sender | examples/udpserver, examples/udpclient |
UDP net.PacketConn wrapper pattern | examples/udppacketconn |
Listener, NewConn, Read, UDP, and TLS API examples | Package examples |
Use the runnable examples above for complete programs. The core API shape is small.
header := proxyproto.HeaderProxyFromAddrs(1, sourceAddr, destinationAddr)
_, err := header.WriteTo(conn) // write the PROXY header before application data
See examples/client for a complete TCP client.
proxyListener := &proxyproto.Listener{Listener: ln}
conn, err := proxyListener.Accept()
// Connections must open with a PROXY header (the default policy is REQUIRE);
// conn.RemoteAddr() then reports the client address from that header.
See examples/server for a complete TCP server. For HTTP/1
and HTTP/2, see examples/httpserver, which uses
helper/http2 so one server can accept proxied HTTP/1 and HTTP/2
connections.
[!WARNING] The zero-value configuration requires the PROXY header but still honors it from any peer. It is not safe for listeners reachable by untrusted clients without a trusted-source policy. See Security.
The PROXY header replaces what your application sees as the client address, so whoever is allowed to send one can spoof their origin. The spec (section 2) says receivers "MUST not try to guess" whether the header is present, and requires access filtering so only trusted proxies can use the protocol.
Important stream defaults and policies:
Listener and NewConn use
proxyproto.DefaultPolicy, which is REQUIRE. A connection that does not
open with a PROXY header fails its first I/O with ErrNoProxyProtocol, so
header presence is never guessed.REQUIRE still honors headers from any peer. Use it only when the listener
is reachable exclusively by trusted proxies, such as a private network
segment behind your load balancer.proxyproto.DefaultPolicy = proxyproto.USE, per
connection with WithPolicy(USE), or per listener with a policy returning
USE.TrustProxyHeaderFrom or
TrustProxyHeaderFromRanges. Trusted peers must send a header; untrusted
peers are dropped by Accept. For example:proxyListener := &proxyproto.Listener{
Listener: ln,
// Connections from the load balancer must open with a PROXY header
// (REQUIRE); connections from any other source are dropped. For CIDR
// ranges, use TrustProxyHeaderFromRanges([]string{"10.0.0.0/24"}).
// For mixed traffic (e.g. optional headers from some sources), spell the
// two policies out with PolicyFromRanges(ranges, matched, unmatched).
ConnPolicy: proxyproto.TrustProxyHeaderFrom(net.ParseIP("10.0.0.10")),
}
For UDP, ParseUDPDatagram has no built-in trusted-source policy because it
only sees packet bytes. Check the net.PacketConn.ReadFrom sender address
yourself before trusting header.SourceAddr.
Related knobs are documented in the
package docs:
ReadHeaderTimeout (default 10s), MaxV2HeaderSize (default 4KiB),
V1AcceptIPv4InTCP6 (default off), and Listener.ValidateHeader.
The spec requires the header and proxied payload in the same UDP datagram, and
the receiver must parse the header independently for every datagram. Listener
and Conn cannot provide those semantics; use ParseUDPDatagram and
Header.FormatUDPDatagram with your own net.PacketConn. Headers with
UDPv4/UDPv6 families carried over stream connections describe the proxied
protocol and remain fully supported.
examples/udpserver and
examples/udpclient show direct per-datagram parsing
and formatting.examples/udppacketconn sketches a
net.PacketConn wrapper with reply routing through the relaying proxy.ExampleParseUDPDatagram
and
ExampleHeader_FormatUDPDatagram
show the same APIs as package examples.The library deliberately does not export a net.PacketConn wrapper. A wrapper
that returns the client address from the header also needs an application-owned
client-to-proxy flow table for replies, including bounds, expiry, and spoofing
policy.
When combining PROXY protocol with TLS, match the wrapper order to the upstream
order. If the header is sent in cleartext before the handshake, put proxyproto
inside TLS: tls.NewListener(&proxyproto.Listener{Listener: l}, tlsConfig).
If the header is sent inside the TLS session, decrypt first:
&proxyproto.Listener{Listener: tls.NewListener(l, tlsConfig)}. In both cases
conn.RemoteAddr() reports the client carried by the PROXY header.
Runnable code lives in examples/tlsserver and
examples/tlsclient. Package examples show both
orderings.
AWS Network Load Balancer (NLB) does not send the PROXY v2 header until the
client sends payload: the target group attribute
proxy_protocol_v2.client_to_server.header_placement defaults to
on_first_ack_with_payload. Server-first protocols such as SMTP, FTP, and SSH
fail in that mode; contact AWS support to change the attribute to
on_first_ack so the header arrives before the backend speaks.
106 followers · starred Sep 2025
258 followers · starred Nov 2023
98 followers · starred Mar 2024
336 followers · starred Mar 2020
A Go library implementation of the PROXY protocol, versions 1 and 2.
Go
625
269 commits
updated Sep 14, 2026
A Go library implementation of the HAProxy PROXY protocol specification 3.4. It covers protocol versions 1 (text) and 2 (binary), carrying the original client connection information across NAT and proxy layers.
Use it on either side of a PROXY-aware hop: send headers with Header.WriteTo
or Header.FormatUDPDatagram, and receive them with Listener, NewConn,
Read, or ParseUDPDatagram. The stream APIs are for TCP and Unix streams;
UDP uses packet helpers because the spec requires a header in every datagram.
$ go get github.com/pires/go-proxyproto
The fastest way to get started is the runnable programs under
examples/ and the API examples on
pkg.go.dev:
| Goal | Where to look |
|---|---|
| Minimal client | examples/client |
| Minimal server | examples/server |
| HTTP server | examples/httpserver |
| Server + client over TLS (PROXY header before TLS) | examples/tlsserver, examples/tlsclient |
| UDP receiver and sender | examples/udpserver, examples/udpclient |
UDP net.PacketConn wrapper pattern | examples/udppacketconn |
Listener, NewConn, Read, UDP, and TLS API examples | Package examples |
Use the runnable examples above for complete programs. The core API shape is small.
header := proxyproto.HeaderProxyFromAddrs(1, sourceAddr, destinationAddr)
_, err := header.WriteTo(conn) // write the PROXY header before application data
See examples/client for a complete TCP client.
proxyListener := &proxyproto.Listener{Listener: ln}
conn, err := proxyListener.Accept()
// Connections must open with a PROXY header (the default policy is REQUIRE);
// conn.RemoteAddr() then reports the client address from that header.
See examples/server for a complete TCP server. For HTTP/1
and HTTP/2, see examples/httpserver, which uses
helper/http2 so one server can accept proxied HTTP/1 and HTTP/2
connections.
[!WARNING] The zero-value configuration requires the PROXY header but still honors it from any peer. It is not safe for listeners reachable by untrusted clients without a trusted-source policy. See Security.
The PROXY header replaces what your application sees as the client address, so whoever is allowed to send one can spoof their origin. The spec (section 2) says receivers "MUST not try to guess" whether the header is present, and requires access filtering so only trusted proxies can use the protocol.
Important stream defaults and policies:
Listener and NewConn use
proxyproto.DefaultPolicy, which is REQUIRE. A connection that does not
open with a PROXY header fails its first I/O with ErrNoProxyProtocol, so
header presence is never guessed.REQUIRE still honors headers from any peer. Use it only when the listener
is reachable exclusively by trusted proxies, such as a private network
segment behind your load balancer.proxyproto.DefaultPolicy = proxyproto.USE, per
connection with WithPolicy(USE), or per listener with a policy returning
USE.TrustProxyHeaderFrom or
TrustProxyHeaderFromRanges. Trusted peers must send a header; untrusted
peers are dropped by Accept. For example:proxyListener := &proxyproto.Listener{
Listener: ln,
// Connections from the load balancer must open with a PROXY header
// (REQUIRE); connections from any other source are dropped. For CIDR
// ranges, use TrustProxyHeaderFromRanges([]string{"10.0.0.0/24"}).
// For mixed traffic (e.g. optional headers from some sources), spell the
// two policies out with PolicyFromRanges(ranges, matched, unmatched).
ConnPolicy: proxyproto.TrustProxyHeaderFrom(net.ParseIP("10.0.0.10")),
}
For UDP, ParseUDPDatagram has no built-in trusted-source policy because it
only sees packet bytes. Check the net.PacketConn.ReadFrom sender address
yourself before trusting header.SourceAddr.
Related knobs are documented in the
package docs:
ReadHeaderTimeout (default 10s), MaxV2HeaderSize (default 4KiB),
V1AcceptIPv4InTCP6 (default off), and Listener.ValidateHeader.
The spec requires the header and proxied payload in the same UDP datagram, and
the receiver must parse the header independently for every datagram. Listener
and Conn cannot provide those semantics; use ParseUDPDatagram and
Header.FormatUDPDatagram with your own net.PacketConn. Headers with
UDPv4/UDPv6 families carried over stream connections describe the proxied
protocol and remain fully supported.
examples/udpserver and
examples/udpclient show direct per-datagram parsing
and formatting.examples/udppacketconn sketches a
net.PacketConn wrapper with reply routing through the relaying proxy.ExampleParseUDPDatagram
and
ExampleHeader_FormatUDPDatagram
show the same APIs as package examples.The library deliberately does not export a net.PacketConn wrapper. A wrapper
that returns the client address from the header also needs an application-owned
client-to-proxy flow table for replies, including bounds, expiry, and spoofing
policy.
When combining PROXY protocol with TLS, match the wrapper order to the upstream
order. If the header is sent in cleartext before the handshake, put proxyproto
inside TLS: tls.NewListener(&proxyproto.Listener{Listener: l}, tlsConfig).
If the header is sent inside the TLS session, decrypt first:
&proxyproto.Listener{Listener: tls.NewListener(l, tlsConfig)}. In both cases
conn.RemoteAddr() reports the client carried by the PROXY header.
Runnable code lives in examples/tlsserver and
examples/tlsclient. Package examples show both
orderings.
AWS Network Load Balancer (NLB) does not send the PROXY v2 header until the
client sends payload: the target group attribute
proxy_protocol_v2.client_to_server.header_placement defaults to
on_first_ack_with_payload. Server-first protocols such as SMTP, FTP, and SSH
fail in that mode; contact AWS support to change the attribute to
on_first_ack so the header arrives before the backend speaks.
106 followers · starred Sep 2025
258 followers · starred Nov 2023
98 followers · starred Mar 2024
336 followers · starred Mar 2020