nats-io/nats-server · error

%w: invalid v1 format

Error message

%w: invalid v1 format

What it means

After reading the v1 header line up to CRLF, the server splits it on whitespace and requires at least one token. An empty line (zero tokens) means the header contains only the 'PROXY ' prefix and nothing else, which cannot identify a protocol or address. This is a malformed-header rejection wrapping errProxyProtoInvalid.

Source

Thrown at server/client_proxyproto.go:176

			if line[i] == '\r' && line[i+1] == '\n' {
				// Found CRLF - keep any over-read bytes for the client parser.
				remaining = append(remaining, line[i+2:]...)
				line = line[:i]
				goto foundCRLF
			}
		}
	}

	// Exceeded max length without finding CRLF
	return nil, nil, fmt.Errorf("%w: v1 line too long", errProxyProtoInvalid)

foundCRLF:
	// Get parts from the protocol
	parts := strings.Fields(string(line))

	// Validate format
	if len(parts) < 1 {
		return nil, nil, fmt.Errorf("%w: invalid v1 format", errProxyProtoInvalid)
	}

	// Handle UNKNOWN (health check, like v2 LOCAL)
	if parts[0] == proxyProtoV1Unknown {
		return nil, remaining, nil
	}

	// Must have exactly 5 parts: protocol, src-ip, dst-ip, src-port, dst-port
	if len(parts) != 5 {
		return nil, nil, fmt.Errorf("%w: invalid v1 format", errProxyProtoInvalid)
	}

	protocol := parts[0]
	srcIP := net.ParseIP(parts[1])
	dstIP := net.ParseIP(parts[2])

	if srcIP == nil || dstIP == nil {
		return nil, nil, fmt.Errorf("%w: invalid address", errProxyProtoInvalid)

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Fix the sender to emit a full v1 header like 'PROXY TCP4 <src> <dst> <sport> <dport>\r\n'
  2. Use 'PROXY UNKNOWN\r\n' for health checks instead of a bare 'PROXY ' prefix
  3. Check the proxy/load-balancer template for a truncated format string
  4. Reproduce the raw bytes the sender writes and compare with the PROXY protocol v1 spec

Example fix

// before
conn.Write([]byte("PROXY \r\n"))
// after
conn.Write([]byte("PROXY TCP4 192.0.2.1 198.51.100.7 35646 4222\r\n")) // or "PROXY UNKNOWN\r\n" for health checks
Defensive patterns

Strategy: validation

Validate before calling

// Sender-side check before writing:
header := "PROXY " + payload // payload after prefix
if len(strings.Fields(header)) < 2 && !strings.HasPrefix(payload, "UNKNOWN") {
    return errors.New("PROXY v1 header needs protocol + 4 address fields, or UNKNOWN")
}

Type guard

func isCompleteV1Header(line string) bool {
    return len(strings.Fields(strings.TrimSuffix(strings.TrimSuffix(line, "\n"), "\r"))) >= 1
}

Try / catch

addr, _, err := readProxyProtoHeader(conn)
if err != nil {
    if errors.Is(err, errProxyProtoInvalid) {
        log.Printf("empty/invalid PROXY v1 header: %v", err)
        conn.Close()
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Client sends exactly 'PROXY \r\n' (or 'PROXY ' followed by only whitespace before CRLF) to a proxy-protocol-enabled listener; strings.Fields yields zero parts.

Common situations: Health-check script sending a bare 'PROXY ' probe; truncated header due to a proxy bug or packet fragmentation combined with premature CRLF; hand-rolled test client with a wrong format string.

Related errors


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