nats-io/nats-server · error

%w: only STREAM protocol supported

Error message

%w: only STREAM protocol supported

What it means

PROXY protocol v2 distinguishes datagram (DGRAM, 0x1) and stream (STREAM, 0x0) transports via the low nibble of the fam/proto byte. This server only accepts TCP streams, so a header declaring DGRAM (UDP) is rejected with errProxyProtoUnsupported (matchable via errors.Is).

Source

Thrown at server/client_proxyproto.go:355

	if command == proxyProtoCmdLocal {
		// For LOCAL, we should skip the address data if any
		if addrLen > 0 {
			// Discard the address data
			if _, err := io.CopyN(io.Discard, conn, int64(addrLen)); err != nil {
				return nil, fmt.Errorf("failed to discard LOCAL command address data: %w", err)
			}
		}
		return nil, nil // nil addr indicates LOCAL command
	}

	// Handle PROXY command
	if command != proxyProtoCmdProxy {
		return nil, fmt.Errorf("unknown PROXY protocol command: 0x%02x", command)
	}

	// Validate protocol (we only support STREAM/TCP)
	if protocol != proxyProtoProtoStream {
		return nil, fmt.Errorf("%w: only STREAM protocol supported", errProxyProtoUnsupported)
	}

	// Parse address data based on family
	var addr *proxyProtoAddr
	var err error
	switch family {
	case proxyProtoFamilyInet:
		addr, err = parseIPv4Addr(conn, addrLen)
	case proxyProtoFamilyInet6:
		addr, err = parseIPv6Addr(conn, addrLen)
	case proxyProtoFamilyUnspec:
		// UNSPEC family with PROXY command is valid but rare
		// Just skip the address data
		if addrLen > 0 {
			if _, err := io.CopyN(io.Discard, conn, int64(addrLen)); err != nil {
				return nil, fmt.Errorf("failed to discard UNSPEC address address data: %w", err)
			}
		}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Reconfigure the proxy frontend/backend so PROXY v2 headers are emitted for TCP (STREAM) traffic
  2. Ensure the header's fam/proto byte uses 0x0 in the protocol nibble for TCP
  3. If UDP support is needed, extend parseProxyProtoV2Header to accept DGRAM and handle the address payload

Example fix

// before (HAProxy)
bind :8475 proto udp
// after
bind :8475 proto tcp ... send-proxy-v2
Defensive patterns

Strategy: validation

Validate before calling

// STREAM = 0x0 in the protocol nibble of the fam/proto byte
if p := hdr[13]&0x0F; p != 0x0 {
    return fmt.Errorf("only STREAM supported, got 0x%x", p)
}

Type guard

func isStreamProto(famProto byte) bool { return famProto&0x0F == 0x0 }

Try / catch

addr, err := readProxyProtoHeader(conn)
if err != nil {
    if errors.Is(err, errProxyProtoUnsupported) {
        // non-TCP traffic to a TCP-only backend: reject and close
        conn.Close()
        return
    }
    return err
}

Prevention

When it happens

Trigger: A sender transmits a v2 header with protocol nibble = 0x1 (DGRAM), typically from a UDP-mode PROXY header emitter, over a TCP connection the server accepts.

Common situations: Proxy configured in UDP proxying mode forwarding headers to a TCP-only backend; miscopied config where the frontend protocol (UDP) doesn't match the backend (TCP); custom senders defaulting to DGRAM.

Related errors


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