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
- Fix the sender to emit a full v1 header like 'PROXY TCP4 <src> <dst> <sport> <dport>\r\n'
- Use 'PROXY UNKNOWN\r\n' for health checks instead of a bare 'PROXY ' prefix
- Check the proxy/load-balancer template for a truncated format string
- 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
- Never send a bare 'PROXY ' line; use 'PROXY UNKNOWN\r\n' for health checks
- Test your proxy's header template end-to-end before deployment
- Use single spaces between fields per spec
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
- %w: invalid protocol %s
- %w: v1 line too long
- %w: invalid address
- invalid source port: %w
- invalid dest port: %w
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/e748e6b3aa2caa37.
Report an issue: GitHub.