{"record":{"id":"2ee0b73d424e6510","repo":"netbirdio/netbird","slug":"private-services-require-at-least-one-access-group","errorCode":null,"errorMessage":"private services require at least one access group","messagePattern":"private services require at least one access group","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"management/internals/modules/reverseproxy/service/service.go","lineNumber":902,"sourceCode":"\tcase ModeTCP, ModeUDP:\n\t\treturn s.validateTCPUDPMode()\n\tcase ModeTLS:\n\t\treturn s.validateTLSMode()\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported mode %q\", s.Mode)\n\t}\n}\n\n// validatePrivateRequirements enforces the private-service contract: HTTP mode, ≥1 access group, no bearer auth.\nfunc (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 {","sourceCodeStart":884,"sourceCodeEnd":920,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/internals/modules/reverseproxy/service/service.go#L884-L920","documentation":"Returned by Service.validatePrivateRequirements (management/internals/modules/reverseproxy/service/service.go:902). A service marked Private is NetBird-only: access is granted exclusively through attached access groups, so at least one must be configured. Zero access groups on a private service makes it unreachable and is rejected before bearer-auth checks run.","triggerScenarios":"Creating/updating a service with Private: true and an empty or missing AccessGroups list.","commonSituations":"Converting a public service to private without first assigning groups; UI toggle for private enabled before group selection; API automation that omits the access_groups field.","solutions":["Attach at least one existing access group to the service (via its update API) together with setting Private.","If no group exists yet, create the group first, then mark the service private.","If you did not intend NetBird-only access, leave Private unset (public/SSO path)."],"exampleFix":"# before\n{\"name\": \"internal-api\", \"private\": true, \"targets\": [\"http://10.0.0.5:8080\"]}\n\n# after\n{\"name\": \"internal-api\", \"private\": true, \"access_groups\": [\"grp-ops\"], \"targets\": [\"http://10.0.0.5:8080\"]}","handlingStrategy":"validation","validationCode":"if svc.Private && len(svc.AccessGroups) == 0 {\n    return fmt.Errorf(\"private services need at least one access group\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Create access groups before marking services private.","Automations should set access_groups in the same request that flips Private."],"tags":["go","netbird","management","reverse-proxy","access-control","validation"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}