netbirdio/netbird · error
domain is required for TCP/UDP services (used for cluster de
Error message
domain is required for TCP/UDP services (used for cluster derivation)
What it means
Returned by validateTCPUDPMode when a tcp or udp service has an empty Domain. Even though L4 traffic is routed by port, the management plane uses the domain to derive the cluster identity that fronts the port, so it is mandatory for every mode. This mirrors validateHTTPMode and validateTLSMode, which also require a domain.
Source
Thrown at management/internals/modules/reverseproxy/service/service.go:922
if s.Auth.BearerAuth != nil && s.Auth.BearerAuth.Enabled {
return errors.New("private services cannot enable bearer auth (SSO): NetBird-only access and SSO are mutually exclusive")
}
return nil
}
func (s *Service) validateHTTPMode() error {
if s.Domain == "" {
return errors.New("service domain is required")
}
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() {View on GitHub (pinned to 93e97f4bf1)
Solutions
- Set the service domain to your account's zone, e.g. "domain": "netbird.example.com".
- Reuse the same domain value you use for HTTP services in that account; it is per-account configuration, not per-protocol.
- Check the API payload actually serializes the field (empty string and omitted field are indistinguishable server-side).
Example fix
// before
{ "name": "db", "mode": "tcp", "listen_port": 5432, "targets": [...] }
// after
{ "name": "db", "mode": "tcp", "domain": "netbird.example.com", "listen_port": 5432, "targets": [...] } Defensive patterns
Strategy: validation
Validate before calling
func checkL4Domain(mode, domain string) error {
if (mode == "tcp" || mode == "udp") && domain == "" {
return errors.New("domain is required for tcp/udp services")
}
return nil
} Type guard
func hasL4Domain(mode, domain string) bool {
return mode != "tcp" && mode != "udp" || domain != ""
} Try / catch
if err := svc.Validate(); err != nil {
if strings.Contains(err.Error(), "domain is required for TCP/UDP") {
return respondBadRequest(fmt.Errorf("set the account domain on the service payload"))
}
return respondBadRequest(err)
} Prevention
- Inject the account domain once from account settings instead of asking per request.
- Assert non-empty required string fields in client-side struct validation before the API call.
- Distinguish omitted vs empty fields in your client serialization (omit_empty tags).
When it happens
Trigger: Creating or updating a service with mode "tcp"/"udp" and no domain field in the request payload; a client that only sends listen_port and targets for L4 services assuming the domain is HTTP-only.
Common situations: Scripts or IaC modules that create port-forwards and skip the domain because 'ports don't need names'. Building an L4 service from scratch via the REST API with a minimal payload. Assuming the management service will auto-generate a domain (it does that for peer-expose requests, not for operator-created services).
Related errors
- auth is not supported for TCP/UDP services
- TCP/UDP services must have exactly one target
- target_id is required for L4 services
- session_idle_timeout must be positive for L4 services
- request_timeout must be positive for L4 services
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/c2b52ac1db037771.
Report an issue: GitHub.