nats-io/nats-server · error

failed to read protocol version: %w

Error message

failed to read protocol version: %w

What it means

During PROXY protocol detection the server could not read the first 6 bytes of the connection (io.ReadFull failed). Those bytes are needed to distinguish PROXY v1 ('PROXY ') from v2 (binary signature), so detection fails and the wrapped underlying I/O error is returned.

Source

Thrown at server/client_proxyproto.go:120

// extracted from the PROXY protocol header
type proxyConn struct {
	net.Conn
	remoteAddr net.Addr
}

// RemoteAddr returns the original client address extracted from PROXY protocol
func (pc *proxyConn) RemoteAddr() net.Addr {
	return pc.remoteAddr
}

// detectProxyProtoVersion reads the first bytes and determines protocol version.
// Returns 1 for v1, 2 for v2, or error.
// The first 6 bytes read are returned so they can be used by the parser.
func detectProxyProtoVersion(conn net.Conn) (version int, header []byte, err error) {
	// Read first 6 bytes to check for "PROXY " or v2 signature
	header = make([]byte, 6)
	if _, err = io.ReadFull(conn, header); err != nil {
		return 0, nil, fmt.Errorf("failed to read protocol version: %w", err)
	}
	switch bytesToString(header) {
	case proxyProtoV1Prefix:
		return 1, header, nil
	case proxyProtoV2Sig[:6]:
		return 2, header, nil
	default:
		// Return the consumed bytes so the caller can replay them into the
		// next protocol layer instead of discarding them.
		return 0, header, errProxyProtoUnrecognized
	}
}

// readProxyProtoV1Header parses PROXY protocol v1 text format.
// Expects the "PROXY " prefix (6 bytes) to have already been consumed.
// Returns any bytes that were read past the trailing CRLF so the caller can
// replay them into the next protocol layer.
func readProxyProtoV1Header(conn net.Conn) (*proxyProtoAddr, []byte, error) {

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Ensure the upstream proxy actually sends a PROXY protocol v1 or v2 header on every connection
  2. Verify the client connects to the correct port (non-PROXY port if it does not send a proxy header)
  3. Check for aggressive health checks/scans closing connections early; point them at a plain monitoring port
  4. Investigate network stability/timeout between the proxy and the server (the wrapped error shows the root cause)

Example fix

// before (no header sent)
nc nats.example.com 9000
// after (send PROXY v1 header first)
printf 'PROXY TCP4 10.0.0.1 10.0.0.2 50000 4222\r\n' | nc nats.example.com 9000
Defensive patterns

Strategy: fallback

Validate before calling

// client side: only connect to a PROXY-protocol port if you will send a header
if !sendProxyHeader {
    port = plainPort // e.g. 4222 instead of the proxy-protocol listener
}

Try / catch

conn, err := dialer.Dial("tcp", addr)
if err != nil {
    // retry with backoff; the server may only accept PROXY-protocol peers
    return retryDial(addr)
}

Prevention

When it happens

Trigger: A client connects to a PROXY-protocol-enabled listener but closes immediately, times out, or sends fewer than 6 bytes; network interruption between proxy and server before the header arrives.

Common situations: Health-check probes that open and close TCP connections, monitoring port scans, a proxy configured for PROXY protocol not actually sending the header, TLS clients connecting to a PROXY-protocol port (TLS bytes < 6 or read stalls).

Related errors


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