netbirdio/netbird · error

proxy_protocol is not supported for UDP services

Error message

proxy_protocol is not supported for UDP services

What it means

Returned by validateTCPUDPMode when mode is "udp" and the single target has proxy_protocol=true. The PROXY protocol prepends a client-address header to a TCP connection; UDP has no connection or byte-stream framing to carry it, so the flag is meaningless and rejected. TCP targets may keep proxy_protocol=true to convey the real client IP to the upstream.

Source

Thrown at management/internals/modules/reverseproxy/service/service.go:931

	}
	if s.ListenPort != 0 {
		return errors.New("listen_port is not supported for HTTP services")
	}
	return s.validateHTTPTargets()
}

func (s *Service) validateTCPUDPMode() error {
	if s.Domain == "" {
		return errors.New("domain is required for TCP/UDP services (used for cluster derivation)")
	}
	if s.isAuthEnabled() {
		return errors.New("auth is not supported for TCP/UDP services")
	}
	if len(s.Targets) != 1 {
		return errors.New("TCP/UDP services must have exactly one target")
	}
	if s.Mode == ModeUDP && s.Targets[0].ProxyProtocol {
		return errors.New("proxy_protocol is not supported for UDP services")
	}
	return s.validateL4Target(s.Targets[0])
}

func (s *Service) validateTLSMode() error {
	if s.Domain == "" {
		return errors.New("domain is required for TLS services (used for SNI matching)")
	}
	if s.isAuthEnabled() {
		return errors.New("auth is not supported for TLS services")
	}
	if s.ListenPort == 0 {
		return errors.New("listen_port is required for TLS services")
	}
	if len(s.Targets) != 1 {
		return errors.New("TLS services must have exactly one target")
	}
	return s.validateL4Target(s.Targets[0])

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Set proxy_protocol to false (or omit it) on the UDP target.
  2. Keep proxy_protocol=true only on TCP targets whose upstream actually parses the PROXY v1 header.
  3. For client identity on UDP, rely on NetBird's network/flow logging rather than the PROXY protocol.

Example fix

// before
{ "mode": "udp", "listen_port": 3478,
  "targets": [ { "target_id": "peer-a", "port": 3478, "proxy_protocol": true } ] }

// after
{ "mode": "udp", "listen_port": 3478,
  "targets": [ { "target_id": "peer-a", "port": 3478, "proxy_protocol": false } ] }
Defensive patterns

Strategy: validation

Validate before calling

func checkUDPNoProxyProtocol(mode string, proxyProtocol bool) error {
	if mode == "udp" && proxyProtocol {
		return errors.New("proxy_protocol must be false for udp services")
	}
	return nil
}

Type guard

func isUDPProxyProtocolClean(mode string, proxyProtocol bool) bool {
	return mode != "udp" || !proxyProtocol
}

Try / catch

if err := svc.Validate(); err != nil {
	if strings.Contains(err.Error(), "proxy_protocol is not supported for UDP") {
		return respondBadRequest(errors.New("set proxy_protocol false on the udp target"))
	}
	return respondBadRequest(err)
}

Prevention

When it happens

Trigger: Creating a udp service with targets[0].proxy_protocol set to true, typically copied from a working TCP service definition that needed client-IP propagation.

Common situations: Duplicating a TCP forward that used proxy_protocol (e.g. in front of an nginx/haproxy expecting it) for a QUIC/Game/UDP service and leaving the flag on. Hoping to log real client IPs for UDP traffic.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/6e98a72a478d2da2. Report an issue: GitHub.