{"record":{"id":"c2b52ac1db037771","repo":"netbirdio/netbird","slug":"domain-is-required-for-tcp-udp-services-used-for","errorCode":null,"errorMessage":"domain is required for TCP/UDP services (used for cluster derivation)","messagePattern":"domain is required for TCP/UDP services \\(used for cluster derivation\\)","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"management/internals/modules/reverseproxy/service/service.go","lineNumber":922,"sourceCode":"\tif s.Auth.BearerAuth != nil && s.Auth.BearerAuth.Enabled {\n\t\treturn errors.New(\"private services cannot enable bearer auth (SSO): NetBird-only access and SSO are mutually exclusive\")\n\t}\n\treturn nil\n}\n\nfunc (s *Service) validateHTTPMode() error {\n\tif s.Domain == \"\" {\n\t\treturn errors.New(\"service domain is required\")\n\t}\n\tif s.ListenPort != 0 {\n\t\treturn errors.New(\"listen_port is not supported for HTTP services\")\n\t}\n\treturn s.validateHTTPTargets()\n}\n\nfunc (s *Service) validateTCPUDPMode() error {\n\tif s.Domain == \"\" {\n\t\treturn errors.New(\"domain is required for TCP/UDP services (used for cluster derivation)\")\n\t}\n\tif s.isAuthEnabled() {\n\t\treturn errors.New(\"auth is not supported for TCP/UDP services\")\n\t}\n\tif len(s.Targets) != 1 {\n\t\treturn errors.New(\"TCP/UDP services must have exactly one target\")\n\t}\n\tif s.Mode == ModeUDP && s.Targets[0].ProxyProtocol {\n\t\treturn errors.New(\"proxy_protocol is not supported for UDP services\")\n\t}\n\treturn s.validateL4Target(s.Targets[0])\n}\n\nfunc (s *Service) validateTLSMode() error {\n\tif s.Domain == \"\" {\n\t\treturn errors.New(\"domain is required for TLS services (used for SNI matching)\")\n\t}\n\tif s.isAuthEnabled() {","sourceCodeStart":904,"sourceCodeEnd":940,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/internals/modules/reverseproxy/service/service.go#L904-L940","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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)."],"exampleFix":"// before\n{ \"name\": \"db\", \"mode\": \"tcp\", \"listen_port\": 5432, \"targets\": [...] }\n\n// after\n{ \"name\": \"db\", \"mode\": \"tcp\", \"domain\": \"netbird.example.com\", \"listen_port\": 5432, \"targets\": [...] }","handlingStrategy":"validation","validationCode":"func checkL4Domain(mode, domain string) error {\n\tif (mode == \"tcp\" || mode == \"udp\") && domain == \"\" {\n\t\treturn errors.New(\"domain is required for tcp/udp services\")\n\t}\n\treturn nil\n}","typeGuard":"func hasL4Domain(mode, domain string) bool {\n\treturn mode != \"tcp\" && mode != \"udp\" || domain != \"\"\n}","tryCatchPattern":"if err := svc.Validate(); err != nil {\n\tif strings.Contains(err.Error(), \"domain is required for TCP/UDP\") {\n\t\treturn respondBadRequest(fmt.Errorf(\"set the account domain on the service payload\"))\n\t}\n\treturn respondBadRequest(err)\n}","preventionTips":["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)."],"tags":["netbird","reverse-proxy","validation","configuration","l4","domain","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}