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
- 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).
- 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.
- 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.
- 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
- Pin the LB config to the exact PROXY variant: send-proxy-v2 for binary v2, send-proxy for v1 text.
- Log/inspect the first bytes of rejected connections to identify misconfigured senders early.
- Never enable PROXY protocol parsing on ports that also accept raw clients.
- Ensure no middleware (TLS terminator, WAF) rewrites the binary header bytes.
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
- 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/5d2ea1704ba36f9b.
Report an issue: GitHub.