{"record":{"id":"65fc817cfa790f31","repo":"nats-io/nats-server","slug":"failed-to-read-v2-header-w","errorCode":null,"errorMessage":"failed to read v2 header: %w","messagePattern":"failed to read v2 header: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/client_proxyproto.go","lineNumber":275,"sourceCode":"\t\t// v1 parser expects \"PROXY \" prefix already consumed\n\t\treturn readProxyProtoV1Header(conn)\n\tcase 2:\n\t\t// Read rest of v2 signature (bytes 6-11, total 6 more bytes)\n\t\tremaining := make([]byte, 6)\n\t\tif _, err := io.ReadFull(conn, remaining); err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"failed to read v2 signature: %w\", err)\n\t\t}\n\n\t\t// Verify full signature\n\t\tfullSig := string(firstBytes) + string(remaining)\n\t\tif fullSig != proxyProtoV2Sig {\n\t\t\treturn nil, nil, fmt.Errorf(\"%w: invalid signature\", errProxyProtoInvalid)\n\t\t}\n\n\t\t// Read rest of header: ver/cmd, fam/proto, addr-len (4 bytes)\n\t\theader := make([]byte, 4)\n\t\tif _, err := io.ReadFull(conn, header); err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"failed to read v2 header: %w\", err)\n\t\t}\n\n\t\t// Continue with parsing\n\t\taddr, err := parseProxyProtoV2Header(conn, header)\n\t\treturn addr, nil, err\n\tdefault:\n\t\treturn nil, nil, fmt.Errorf(\"unsupported PROXY protocol version: %d\", version)\n\t}\n}\n\n// 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 {","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/client_proxyproto.go#L257-L293","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: probe sends partial header then closes\n// conn.Write([]byte(\"\\x0D\\x0A\\x0D\\x0A\\x00\\x0D\\x0A\\x51\\x55\\x49\\x54\\x0A\")); conn.Close()\n// after: probe sends full LOCAL v2 header before any close\n// hdr := append([]byte(\"\\x0D\\x0A\\x0D\\x0A\\x00\\x0D\\x0A\\x51\\x55\\x49\\x54\\x0A\"), 0x20, 0x00, 0x00, 0x00)\n// conn.Write(hdr); conn.Close()","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isTimeout(err error) bool {\n\tvar ne net.Error\n\tif errors.As(err, &ne) {\n\t\treturn ne.Timeout()\n\t}\n\treturn errors.Is(err, os.ErrDeadlineExceeded)\n}","tryCatchPattern":"addr, extra, err := readProxyProtoHeader(conn)\nif err != nil {\n\tswitch {\n\tcase isTimeout(err):\n\t\t// slow/malicious client: drop connection\n\tcase errors.Is(err, io.ErrUnexpectedEOF):\n\t\t// peer closed mid-header: likely a probe; ignore or rate-limit\n\tdefault:\n\t\t// unexpected I/O error: alert on persistence\n\t}\n\tconn.Close()\n}","preventionTips":["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."],"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"}