nats-io/nats-server · error
failed to read v2 header: %w
Error message
failed to read v2 header: %w
What it means
This error is returned when, after the 12-byte v2 signature has been validated, io.ReadFull fails to read the remaining 4 fixed header bytes (ver/cmd, fam/proto, addr-len). The underlying I/O error (EOF, connection reset, timeout) is wrapped with %w so callers can inspect it with errors.Is/errors.As. It indicates the peer closed or stalled mid-header, not a semantic protocol violation.
Source
Thrown at server/client_proxyproto.go:275
// 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.
// If the command is LOCAL (health check), it returns nil for addr and no error.
// If the command is PROXY, it returns the parsed address information.
// The connection must be fresh (no data read yet).
func readProxyProtoV2Header(conn net.Conn) (*proxyProtoAddr, error) {
// Set read deadline to prevent hanging on slow/malicious clients
if err := conn.SetReadDeadline(time.Now().Add(proxyProtoReadTimeout)); err != nil {View on GitHub (pinned to 3a66a489d2)
Solutions
- Check the wrapped cause with errors.Is(err, os.ErrDeadlineExceeded) or net.Error.Timeout() to distinguish slow clients (timeout) from early disconnects (EOF/reset).
- Ensure the upstream proxy writes the complete 16-byte v2 header atomically and does not close the connection before finishing it.
- Increase LB/proxy idle timeouts or fix probe tools that open and immediately close connections; probes should send a full LOCAL v2 header or just close cleanly.
- If timeouts are frequent from slow clients, review proxyProtoReadTimeout (5s) and network latency between proxy and server.
Example fix
// before: probe sends partial header then closes
// conn.Write([]byte("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A")); conn.Close()
// after: probe sends full LOCAL v2 header before any close
// hdr := append([]byte("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"), 0x20, 0x00, 0x00, 0x00)
// conn.Write(hdr); conn.Close() Defensive patterns
Strategy: type-guard
Type guard
func isTimeout(err error) bool {
var ne net.Error
if errors.As(err, &ne) {
return ne.Timeout()
}
return errors.Is(err, os.ErrDeadlineExceeded)
} Try / catch
addr, extra, err := readProxyProtoHeader(conn)
if err != nil {
switch {
case isTimeout(err):
// slow/malicious client: drop connection
case errors.Is(err, io.ErrUnexpectedEOF):
// peer closed mid-header: likely a probe; ignore or rate-limit
default:
// unexpected I/O error: alert on persistence
}
conn.Close()
} Prevention
- Ensure health probes send a complete 16-byte LOCAL v2 header or close without writing.
- Set LB/proxy idle timeouts comfortably above the 5s proxyProtoReadTimeout.
- Monitor rates of this error; spikes indicate network flakiness or a broken upstream proxy upgrade.
- Have the proxy write the full header in one write call to avoid interleaved partial sends.
When it happens
Trigger: readProxyProtoHeader detects v2, validates the full 12-byte signature, then calls io.ReadFull(conn, header) for 4 more bytes; the connection returns fewer than 4 bytes (io.ErrUnexpectedEOF), times out against the 5-second proxyProtoReadTimeout deadline, or resets before the header completes.
Common situations: Health-check probes that open a TCP connection, send only the signature (or partial bytes), and immediately close; network interruption between the proxy and the server; an aggressive idle-timeout on the LB killing half-written headers; a slow or malicious client that never completes the header and hits the read deadline.
Related errors
- failed to read PROXY protocol header: %w
- failed to read v2 signature: %w
- failed to discard LOCAL command address data: %w
- failed to discard UNSPEC address address data: %w
- failed to read IPv4 address data: %w
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/65fc817cfa790f31.
Report an issue: GitHub.