nats-io/nats-server · error

%w: invalid signature

Error message

%w: invalid signature

What it means

This error is returned by readProxyProtoHeader when a connection declared as PROXY protocol v2 has bytes 0-11 that do not exactly match the 12-byte v2 binary signature \r\n\r\n\x00\r\nQUIT\n. It wraps errProxyProtoInvalid ("invalid PROXY protocol header"), so callers can match it with errors.Is(err, errProxyProtoInvalid). The version was detected as v2 from the first 6 bytes, but the following 6 bytes diverged, meaning the sender is not speaking valid PROXY protocol v2.

Source

Thrown at server/client_proxyproto.go:269

	if err != nil {
		return nil, firstBytes, err
	}

	switch version {
	case 1:
		// v1 parser expects "PROXY " prefix already consumed
		return readProxyProtoV1Header(conn)
	case 2:
		// Read rest of v2 signature (bytes 6-11, total 6 more bytes)
		remaining := make([]byte, 6)
		if _, err := io.ReadFull(conn, remaining); err != nil {
			return nil, nil, fmt.Errorf("failed to read v2 signature: %w", err)
		}

		// Verify full signature
		fullSig := string(firstBytes) + string(remaining)
		if fullSig != proxyProtoV2Sig {
			return nil, nil, fmt.Errorf("%w: invalid signature", errProxyProtoInvalid)
		}

		// Read rest of header: ver/cmd, fam/proto, addr-len (4 bytes)
		header := make([]byte, 4)
		if _, err := io.ReadFull(conn, header); err != nil {
			return nil, nil, fmt.Errorf("failed to read v2 header: %w", err)
		}

		// Continue with parsing
		addr, err := parseProxyProtoV2Header(conn, header)
		return addr, nil, err
	default:
		return nil, nil, fmt.Errorf("unsupported PROXY protocol version: %d", version)
	}
}

// readProxyProtoV2Header is kept for backward compatibility and direct testing.
// It reads and parses a PROXY protocol v2 header from the connection.

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Fix the upstream proxy configuration so it actually emits PROXY protocol v2 (e.g. HAProxy 'send-proxy-v2' not 'send-proxy' for v1, or vice versa per listener expectations).
  2. If clients do not send PROXY protocol at all, disable proxy protocol on that listener/port; the first 6 bytes of a non-PROXY client will otherwise match or corrupt detection.
  3. Capture the raw first bytes of the offending connection and compare against the expected signature \x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A to identify what the sender is actually emitting.
  4. Verify no middleware/TLS terminator is mangling binary bytes; PROXY v2 is binary and must not pass through anything that transforms the byte stream.

Example fix

// before: server expects v2 but LB sends v1 text lines
// haproxy.cfg
//   server nats1 10.0.0.1:4222 send-proxy
// after: configure v2 to match the binary signature check
//   server nats1 10.0.0.1:4222 send-proxy-v2
Defensive patterns

Strategy: validation

Validate before calling

// Validate the first bytes of a connection before enabling PROXY protocol parsing:
// the sender must begin with the 12-byte v2 signature.
func looksLikeProxyV2(first []byte) bool {
	sig := []byte("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A")
	return len(first) >= 12 && string(first[:12]) == string(sig)
}

Type guard

func isProxyProtoInvalid(err error) bool {
	return errors.Is(err, errProxyProtoInvalid)
}

Try / catch

addr, extra, err := readProxyProtoHeader(conn)
if err != nil {
	if errors.Is(err, errProxyProtoInvalid) {
		// treat as non-PROXY or misconfigured sender: log and close
		conn.Close()
		return
	}
	// I/O or other failure
	return
}

Prevention

When it happens

Trigger: detectProxyProtoVersion reads 6 bytes matching the first half of the v2 signature (proxyProtoV2Sig[:6]); the code then io.ReadFull's the remaining 6 signature bytes, concatenates them, and the full 12-byte string differs from proxyProtoV2Sig. This happens when a client sends data whose first 6 bytes coincidentally match \x0D\x0A\x0D\x0A\x00\x0D but is not a PROXY v2 header, or when a truncated/corrupted v2 header arrives.

Common situations: A load balancer (HAProxy, AWS NLB, Envoy) is configured for a different PROXY protocol variant than the client actually sends; a health checker or garbage/malicious client sends binary data that happens to start with the signature prefix; a proxy sends a truncated header; proxy protocol is enabled on the NATS server but the connecting peer actually sends plain client protocol (the first bytes are consumed and cannot be replayed for the v2 path).

Related errors


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