nats-io/nats-server · error
%w: invalid version 0x%02x
Error message
%w: invalid version 0x%02x
What it means
This error indicates the PROXY protocol v2 header's version nibble does not match the required v2 version (0x2). The version is the high nibble of the ver/cmd byte, masked and compared against proxyProtoV2Ver. It is wrapped in errProxyProtoInvalid, so callers can match it with errors.Is to reject malformed or non-v2 PROXY protocol payloads.
Source
Thrown at server/client_proxyproto.go:325
// Validate signature (first 12 bytes)
if string(header[:12]) != proxyProtoV2Sig {
return nil, fmt.Errorf("%w: invalid signature", errProxyProtoInvalid)
}
// Continue with parsing after signature
return parseProxyProtoV2Header(conn, header[12:16])
}
// parseProxyProtoV2Header parses v2 protocol after signature has been validated.
// header contains the 4 bytes: ver/cmd, fam/proto, addr-len (2 bytes).
func parseProxyProtoV2Header(conn net.Conn, header []byte) (*proxyProtoAddr, error) {
// Parse version and command
verCmd := header[0]
version := verCmd & proxyProtoV2VerMask
command := verCmd & proxyProtoCmdMask
if version != proxyProtoV2Ver {
return nil, fmt.Errorf("%w: invalid version 0x%02x", errProxyProtoInvalid, version)
}
// Parse address family and protocol
famProto := header[1]
family := famProto & proxyProtoFamilyMask
protocol := famProto & proxyProtoProtoMask
// Parse address length (big-endian uint16)
addrLen := binary.BigEndian.Uint16(header[2:4])
// Handle LOCAL command (health check)
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)
}View on GitHub (pinned to 3a66a489d2)
Solutions
- Ensure the upstream proxy is configured for PROXY protocol v2 binary output, not v1 text
- Verify the sender actually prepends the 16-byte v2 signature+header before application data
- Capture the first bytes from the client and confirm the ver/cmd byte is 0x20-0x2F
- If you must accept v1, enable/expect v1 parsing instead of feeding v1 into the v2 path
Example fix
// before (proxy sending v1 text) PROXY TCP4 192.0.2.1 192.0.2.2 4242 443 // after (HAProxy v2 binary) server ... send-proxy-v2
Defensive patterns
Strategy: validation
Validate before calling
// Verify the ver/cmd byte before sending: version nibble must be 0x2
if hdr[12]&0xF0 != 0x20 {
return fmt.Errorf("not a PROXY v2 header: ver/cmd=0x%02x", hdr[12])
} Type guard
func isProxyV2VerCmd(b byte) bool { return b&0xF0 == 0x20 } Try / catch
addr, err := readProxyProtoHeader(conn)
if err != nil {
if errors.Is(err, errProxyProtoInvalid) {
// reject/malformed client: log source IP and close
conn.Close()
return
}
return err
} Prevention
- Configure load balancers for send-proxy-v2, never v1, when the server expects v2
- Unit-test senders against the 16-byte v2 signature + 0x21 ver/cmd
- Log the raw first bytes on parse failure to speed diagnosis
- Keep one PROXY protocol version across the whole chain
When it happens
Trigger: A client (or proxy) sends a binary v2 signature but the ver/cmd byte's high nibble is not 0x2 — e.g. a v1 ASCII 'PROXY ...' line misdetected as v2, a corrupted byte, or a hypothetical v3 header.
Common situations: Load balancer (HAProxy, NGINX, AWS NLB) misconfigured to send PROXY v1 text while the server is in v2 binary mode; a raw TCP client fuzzing or sending garbage that happens to start with the v2 signature; middleware mangling the first bytes of the stream.
Related errors
- failed to read protocol version: %w
- failed to read v1 line: %w
- %w: v1 line too long
- %w: invalid v1 format
- %w: invalid address
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/da48f44077885665.
Report an issue: GitHub.