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
- Set proxy_protocol to false (or omit it) on the UDP target.
- Keep proxy_protocol=true only on TCP targets whose upstream actually parses the PROXY v1 header.
- 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
- Default proxy_protocol to false and enable it per-target only for TCP behind a PROXY-aware upstream.
- Document in service templates that PROXY protocol is TCP-only (v1 text header needs a stream).
- Lint UDP service configs for the flag in CI before they hit the API.
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
- domain is required for TCP/UDP services (used for cluster de
- auth is not supported for TCP/UDP services
- TCP/UDP services must have exactly one target
- auth is not supported for TLS services
- TLS services must have exactly one target
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/6e98a72a478d2da2.
Report an issue: GitHub.