{"record":{"id":"d3799cd4a31d3aca","repo":"netbirdio/netbird","slug":"service-domain-is-required","errorCode":null,"errorMessage":"service domain is required","messagePattern":"service domain is required","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"management/internals/modules/reverseproxy/service/service.go","lineNumber":912,"sourceCode":"func (s *Service) validatePrivateRequirements() error {\n\tif !s.Private {\n\t\treturn nil\n\t}\n\tif s.Mode != \"\" && s.Mode != ModeHTTP {\n\t\treturn fmt.Errorf(\"private services only support HTTP mode, got %q\", s.Mode)\n\t}\n\tif len(s.AccessGroups) == 0 {\n\t\treturn errors.New(\"private services require at least one access group\")\n\t}\n\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 {","sourceCodeStart":894,"sourceCodeEnd":930,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/internals/modules/reverseproxy/service/service.go#L894-L930","documentation":"Returned by Service.validateHTTPMode (management/internals/modules/reverseproxy/service/service.go:912) for services in HTTP mode (the default mode, applied when Mode is empty). HTTP services are exposed on a domain, so Domain must be set before target validation (validateHTTPTargets) proceeds; listen ports are not permitted for HTTP mode.","triggerScenarios":"Creating/updating a service with mode http (or omitted) and no Domain field; templates that only set targets and name.","commonSituations":"First-time service creation where the author assumed the domain is auto-generated; migrating TCP-style payloads (domain-less) into an HTTP-mode service; UI saving before the domain field is filled.","solutions":["Set Domain to the hostname the service will be served on.","If you intended a raw TCP/UDP listener instead, set Mode explicitly to tcp/udp (which derives the cluster from the domain for other purposes and has its own domain requirement).","Validate name/targets/domain together client-side before submitting."],"exampleFix":"# before\n{\"name\": \"internal-api\", \"mode\": \"http\", \"targets\": [\"http://10.0.0.5:8080\"]}\n\n# after\n{\"name\": \"internal-api\", \"mode\": \"http\", \"domain\": \"api.example.net\", \"targets\": [\"http://10.0.0.5:8080\"]}","handlingStrategy":"validation","validationCode":"mode := svc.Mode\nif mode == \"\" {\n    mode = ModeHTTP\n}\nif mode == ModeHTTP && svc.Domain == \"\" {\n    return fmt.Errorf(\"domain is required for HTTP services\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["HTTP-mode services need a domain; provide it in every create/update payload.","For raw TCP/UDP listeners set mode explicitly rather than relying on the HTTP default."],"tags":["go","netbird","management","reverse-proxy","validation","http"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}