nats-io/nats-server · error
%w: invalid protocol %s
Error message
%w: invalid protocol %s
What it means
The first field of the v1 header was neither TCP4, TCP6, nor the earlier-handled UNKNOWN. The PROXY protocol v1 spec only defines those keywords, so any other protocol token (e.g. UDP4, TCP, or garbage) makes the header invalid and the server rejects it with errProxyProtoInvalid, including the offending value in the message.
Source
Thrown at server/client_proxyproto.go:222
if err != nil {
return nil, nil, fmt.Errorf("invalid dest port: %w", err)
}
// Validate protocol matches IP version. The textual form determines the
// family: TCP4 requires dotted-quad addresses, TCP6 requires IPv6
// addresses. IPv4-mapped IPv6 addresses (e.g. "::ffff:192.0.2.1") are
// valid for TCP6 since dual-stack proxies can emit those for IPv4
// clients on IPv6 sockets, matching the v2 parser behavior.
srcIsV6 := strings.Contains(parts[1], ":")
dstIsV6 := strings.Contains(parts[2], ":")
if protocol == proxyProtoV1TCP4 && (srcIsV6 || dstIsV6) {
return nil, nil, fmt.Errorf("%w: TCP4 with IPv6 address", errProxyProtoInvalid)
}
if protocol == proxyProtoV1TCP6 && (!srcIsV6 || !dstIsV6) {
return nil, nil, fmt.Errorf("%w: TCP6 with IPv4 address", errProxyProtoInvalid)
}
if protocol != proxyProtoV1TCP4 && protocol != proxyProtoV1TCP6 {
return nil, nil, fmt.Errorf("%w: invalid protocol %s", errProxyProtoInvalid, protocol)
}
return &proxyProtoAddr{
srcIP: srcIP,
srcPort: uint16(srcPort),
dstIP: dstIP,
dstPort: uint16(dstPort),
}, remaining, nil
}
// readProxyProtoHeader reads and parses PROXY protocol (v1 or v2) from the connection.
// Automatically detects version and routes to appropriate parser.
// If the command is LOCAL/UNKNOWN (health check), it returns nil for addr and no error.
// If the command is PROXY, it returns the parsed address information.
// It also returns any bytes that were read past the v1 header terminator so the
// caller can replay them into the normal client parser.
// The connection must be fresh (no data read yet).
func readProxyProtoHeader(conn net.Conn) (*proxyProtoAddr, []byte, error) {View on GitHub (pinned to 3a66a489d2)
Solutions
- Change the sender to emit only TCP4 or TCP6 (or UNKNOWN for health checks)
- Fix case-sensitivity: the keyword must be uppercase TCP4/TCP6
- If you need UDP proxying, this server does not support it via PROXY protocol v1 - use a supported transport
- Correct typo'd protocol keywords in test clients and templates
Example fix
// before "PROXY UDP4 192.0.2.1 198.51.100.7 35646 4222\r\n" // after "PROXY TCP4 192.0.2.1 198.51.100.7 35646 4222\r\n"
Defensive patterns
Strategy: validation
Validate before calling
if proto != "TCP4" && proto != "TCP6" && proto != "UNKNOWN" {
return fmt.Errorf("unsupported v1 protocol keyword %q; use TCP4/TCP6/UNKNOWN", proto)
} Type guard
func isKnownV1Protocol(s string) bool {
switch s {
case "TCP4", "TCP6", "UNKNOWN":
return true
}
return false
} Try / catch
_, _, err := readProxyProtoHeader(conn)
if err != nil {
if errors.Is(err, errProxyProtoInvalid) && strings.Contains(err.Error(), "invalid protocol") {
log.Printf("peer sent unsupported v1 protocol keyword: %v", err)
return
}
return err
} Prevention
- Only emit uppercase TCP4, TCP6, or UNKNOWN
- This server does not accept UDP keywords (UDP4/UDP6) in v1 headers
- Validate sender templates with a keyword whitelist before deploy
When it happens
Trigger: Header like 'PROXY UDP4 192.0.2.1 198.51.100.7 35646 4222\r\n' or 'PROXY tcp4 ...' (lowercase); reached only when there are exactly 5 fields and the keyword is not TCP4/TCP6/UNKNOWN.
Common situations: Proxy supporting UDP proxy-protocol extensions not supported by this server; case-sensitivity bugs in custom senders; test fixtures with typos like 'PROXY TCP 192.0.2.1 ...'.
Related errors
- %w: invalid v1 format
- %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/512d605b6fde1bcf.
Report an issue: GitHub.