nats-io/nats-server · error

failed to read v2 signature: %w

Error message

failed to read v2 signature: %w

What it means

After detecting the v2 signature's first 6 bytes, the server reads 6 more bytes to complete the 12-byte binary signature. If that read fails (connection closed early, timeout after 5s, or reset), the error wraps the underlying io error with this message. It indicates the sender started a v2 header but the bytes never arrived or the connection died mid-header.

Source

Thrown at server/client_proxyproto.go:263

	defer conn.SetReadDeadline(time.Time{})

	// Detect version.
	// On errProxyProtoUnrecognized, firstBytes holds the bytes that were
	// consumed so the caller can replay them.
	version, firstBytes, err := detectProxyProtoVersion(conn)
	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:

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Verify the upstream proxy writes the full 12-byte v2 signature atomically
  2. Check network stability and firewall/MTU settings between proxy and server for truncation
  3. Look at the wrapped cause: io.EOF/ErrUnexpectedEOF means the sender closed early; i/o timeout means the sender stalled past 5s
  4. Ensure health checkers either send a complete header or close cleanly without writing partial signatures

Example fix

// before: partial signature write then close
conn.Write([]byte("\x0D\x0A\x0D\x0A\x00\x0D"))
conn.Close()
// after: write full 12-byte signature + header
sig := "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
conn.Write([]byte(sig + "\x20\x11\x00\x0C" /* + address bytes */))
Defensive patterns

Strategy: try-catch

Validate before calling

// Sender: write the entire v2 header (signature + header + addr) in one Write:
buf := append([]byte(sig12Bytes), hdrAndAddr...)
if _, err := conn.Write(buf); err != nil { return err }

Type guard

func isTimeoutErr(err error) bool {
    var ne net.Error
    return errors.As(err, &ne) && ne.Timeout()
}

Try / catch

addr, _, err := readProxyProtoHeader(conn)
if err != nil {
    if strings.Contains(err.Error(), "failed to read v2 signature") {
        if isTimeoutErr(err) {
            log.Printf("peer stalled while sending v2 signature (5s deadline): %v", err)
        } else if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
            log.Printf("peer closed mid v2 signature: %v", err)
        }
        return
    }
    return err
}

Prevention

When it happens

Trigger: A client writes the first 6 signature bytes (\r\n\r\n\x00\r\n) then closes or stalls before sending the remaining 6 bytes (QUIT\n); network interruption between proxy and server; read deadline expiry during signature read.

Common situations: Proxy crashing mid-handshake; MTU/firewall truncating the header write; client sending only a partial signature to probe the port; slow health-checker hitting the 5-second read timeout.

Related errors


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