{"record":{"id":"e1331d424e2fa393","repo":"nats-io/nats-server","slug":"failed-to-read-proxy-protocol-header-w","errorCode":null,"errorMessage":"failed to read PROXY protocol header: %w","messagePattern":"failed to read PROXY protocol header: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/client_proxyproto.go","lineNumber":304,"sourceCode":"// readProxyProtoV2Header is kept for backward compatibility and direct testing.\n// It reads and parses a PROXY protocol v2 header from the connection.\n// If the command is LOCAL (health check), it returns nil for addr and no error.\n// If the command is PROXY, it returns the parsed address information.\n// The connection must be fresh (no data read yet).\nfunc readProxyProtoV2Header(conn net.Conn) (*proxyProtoAddr, error) {\n\t// Set read deadline to prevent hanging on slow/malicious clients\n\tif err := conn.SetReadDeadline(time.Now().Add(proxyProtoReadTimeout)); err != nil {\n\t\treturn nil, err\n\t}\n\tdefer conn.SetReadDeadline(time.Time{})\n\n\t// Read fixed header (16 bytes)\n\theader := make([]byte, proxyProtoV2HeaderSize)\n\tif _, err := io.ReadFull(conn, header); err != nil {\n\t\tif ne, ok := err.(net.Error); ok && ne.Timeout() {\n\t\t\treturn nil, errProxyProtoTimeout\n\t\t}\n\t\treturn nil, fmt.Errorf(\"failed to read PROXY protocol header: %w\", err)\n\t}\n\n\t// Validate signature (first 12 bytes)\n\tif string(header[:12]) != proxyProtoV2Sig {\n\t\treturn nil, fmt.Errorf(\"%w: invalid signature\", errProxyProtoInvalid)\n\t}\n\n\t// Continue with parsing after signature\n\treturn parseProxyProtoV2Header(conn, header[12:16])\n}\n\n// parseProxyProtoV2Header parses v2 protocol after signature has been validated.\n// header contains the 4 bytes: ver/cmd, fam/proto, addr-len (2 bytes).\nfunc parseProxyProtoV2Header(conn net.Conn, header []byte) (*proxyProtoAddr, error) {\n\t// Parse version and command\n\tverCmd := header[0]\n\tversion := verCmd & proxyProtoV2VerMask\n\tcommand := verCmd & proxyProtoCmdMask","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/client_proxyproto.go#L286-L322","documentation":"In the backward-compatible readProxyProtoV2Header helper, io.ReadFull fails while reading the 16-byte fixed v2 header. Read timeouts (net.Error with Timeout() true) are mapped to the dedicated sentinel errProxyProtoTimeout; all other read failures (EOF, unexpected EOF, connection reset) are wrapped with this message via %w. It signals an incomplete v2 header on the wire, not a semantic parse failure.","triggerScenarios":"readProxyProtoV2Header calls io.ReadFull(conn, header) with proxyProtoV2HeaderSize (16) bytes; the peer sends fewer than 16 bytes before closing or resetting (io.ErrUnexpectedEOF, ECONNRESET), so the wrap at line 304 fires. Direct tests (TestClientProxyProtoV2Parse*) drive this via a net.Pipe-style conn.","commonSituations":"Health probes that open a connection and close without sending a full header; clients speaking PROXY v1 text or no PROXY protocol at all on a port expecting v2 (their short/odd payloads fail the 16-byte read); flaky network links dropping the connection mid-header; load balancers with short connection idle timeouts.","solutions":["Inspect the wrapped error: errors.Is(err, io.ErrUnexpectedEOF) means the peer closed early; check whether the sender actually emits the full 16-byte v2 header.","Match sender and receiver configuration: the peer must be set to send-proxy-v2 (binary); a v1 sender on a v2-only path will fail header parsing.","For probe/monitoring clients, send a complete LOCAL command v2 header (16 bytes, ver/cmd 0x20, fam/proto 0x00, len 0x0000) or close without writing.","If the error persists under load, check for LB idle timeouts or MTU/proxy truncation between the proxy tier and the server."],"exampleFix":"// before: v1-format sender against v2 parser\n// conn.Write([]byte(\"PROXY TCP4 1.2.3.4 5.6.7.8 1000 2000\\r\\n\"))\n// after: send a binary v2 header\n// sig := []byte(\"\\x0D\\x0A\\x0D\\x0A\\x00\\x0D\\x0A\\x51\\x55\\x49\\x54\\x0A\")\n// conn.Write(append(sig, 0x20, 0x11, 0x00, 0x0C, /* 12 bytes IPv4 addr data */...))","handlingStrategy":"type-guard","validationCode":"// Before treating a conn as PROXY v2, ensure you can expect at least 16 bytes:\n// there is no pre-call validation for a stream, but you can pre-classify errors:\n// readProxyProtoV2Header already maps timeouts to errProxyProtoTimeout.\nfunc expectFullV2Header(senderConfiguredForV2 bool) error {\n\tif !senderConfiguredForV2 {\n\t\treturn errors.New(\"upstream not configured for PROXY v2; header read will fail\")\n\t}\n\treturn nil\n}","typeGuard":"func classifyV2HeaderReadErr(err error) string {\n\tif errors.Is(err, errProxyProtoTimeout) {\n\t\treturn \"timeout\"\n\t}\n\tif errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) {\n\t\treturn \"peer-closed-early\"\n\t}\n\tvar ne net.Error\n\tif errors.As(err, &ne) {\n\t\treturn \"net-error\"\n\t}\n\treturn \"other\"\n}","tryCatchPattern":"addr, err := readProxyProtoV2Header(conn)\nif err != nil {\n\tif errors.Is(err, errProxyProtoTimeout) {\n\t\t// slow client: drop silently\n\t} else if errors.Is(err, io.ErrUnexpectedEOF) {\n\t\t// probe or misconfigured sender: count metric, close\n\t}\n\tconn.Close()\n\treturn\n}","preventionTips":["Verify the upstream proxy emits the full 16-byte binary v2 header atomically.","Use readProxyProtoHeader (version-detecting) unless v2-only input is guaranteed.","Keep probe tooling sending full LOCAL headers; empty open/close probes will surface as this error.","Alert on error-rate changes: EOF-class spikes mean sender config drift; timeout spikes mean slow clients."],"tags":["proxy-protocol","network","io","truncated-header"],"backgroundTag":"proxy-protocol-truncated-header","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}