nats-io/nats-server · error
%w: only STREAM protocol supported
Error message
%w: only STREAM protocol supported
What it means
PROXY protocol v2 distinguishes datagram (DGRAM, 0x1) and stream (STREAM, 0x0) transports via the low nibble of the fam/proto byte. This server only accepts TCP streams, so a header declaring DGRAM (UDP) is rejected with errProxyProtoUnsupported (matchable via errors.Is).
Source
Thrown at server/client_proxyproto.go:355
if command == proxyProtoCmdLocal {
// For LOCAL, we should skip the address data if any
if addrLen > 0 {
// Discard the address data
if _, err := io.CopyN(io.Discard, conn, int64(addrLen)); err != nil {
return nil, fmt.Errorf("failed to discard LOCAL command address data: %w", err)
}
}
return nil, nil // nil addr indicates LOCAL command
}
// Handle PROXY command
if command != proxyProtoCmdProxy {
return nil, fmt.Errorf("unknown PROXY protocol command: 0x%02x", command)
}
// Validate protocol (we only support STREAM/TCP)
if protocol != proxyProtoProtoStream {
return nil, fmt.Errorf("%w: only STREAM protocol supported", errProxyProtoUnsupported)
}
// Parse address data based on family
var addr *proxyProtoAddr
var err error
switch family {
case proxyProtoFamilyInet:
addr, err = parseIPv4Addr(conn, addrLen)
case proxyProtoFamilyInet6:
addr, err = parseIPv6Addr(conn, addrLen)
case proxyProtoFamilyUnspec:
// UNSPEC family with PROXY command is valid but rare
// Just skip the address data
if addrLen > 0 {
if _, err := io.CopyN(io.Discard, conn, int64(addrLen)); err != nil {
return nil, fmt.Errorf("failed to discard UNSPEC address address data: %w", err)
}
}View on GitHub (pinned to 3a66a489d2)
Solutions
- Reconfigure the proxy frontend/backend so PROXY v2 headers are emitted for TCP (STREAM) traffic
- Ensure the header's fam/proto byte uses 0x0 in the protocol nibble for TCP
- If UDP support is needed, extend parseProxyProtoV2Header to accept DGRAM and handle the address payload
Example fix
// before (HAProxy) bind :8475 proto udp // after bind :8475 proto tcp ... send-proxy-v2
Defensive patterns
Strategy: validation
Validate before calling
// STREAM = 0x0 in the protocol nibble of the fam/proto byte
if p := hdr[13]&0x0F; p != 0x0 {
return fmt.Errorf("only STREAM supported, got 0x%x", p)
} Type guard
func isStreamProto(famProto byte) bool { return famProto&0x0F == 0x0 } Try / catch
addr, err := readProxyProtoHeader(conn)
if err != nil {
if errors.Is(err, errProxyProtoUnsupported) {
// non-TCP traffic to a TCP-only backend: reject and close
conn.Close()
return
}
return err
} Prevention
- Match proxy frontend transport (tcp vs udp) to the backend before enabling send-proxy-v2
- Only enable PROXY protocol on TCP listeners
- Document that this server is TCP/STREAM-only
- Validate fam/proto byte 0x00/0x10/0x20/0x30 (all STREAM) in sender tests
When it happens
Trigger: A sender transmits a v2 header with protocol nibble = 0x1 (DGRAM), typically from a UDP-mode PROXY header emitter, over a TCP connection the server accepts.
Common situations: Proxy configured in UDP proxying mode forwarding headers to a TCP-only backend; miscopied config where the frontend protocol (UDP) doesn't match the backend (TCP); custom senders defaulting to DGRAM.
Related errors
- failed to read protocol version: %w
- failed to read v1 line: %w
- %w: v1 line too long
- failed to read v2 signature: %w
- %w: invalid v1 format
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/66ed8cfc15f5603d.
Report an issue: GitHub.