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

  1. Ensure the upstream proxy is configured for PROXY protocol v2 binary output, not v1 text
  2. Verify the sender actually prepends the 16-byte v2 signature+header before application data
  3. Capture the first bytes from the client and confirm the ver/cmd byte is 0x20-0x2F
  4. 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

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


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/da48f44077885665. Report an issue: GitHub.