netbirdio/netbird · error

private services require at least one access group

Error message

private services require at least one access group

What it means

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.

Source

Thrown at management/internals/modules/reverseproxy/service/service.go:902

	case ModeTCP, ModeUDP:
		return s.validateTCPUDPMode()
	case ModeTLS:
		return s.validateTLSMode()
	default:
		return fmt.Errorf("unsupported mode %q", s.Mode)
	}
}

// validatePrivateRequirements enforces the private-service contract: HTTP mode, ≥1 access group, no bearer auth.
func (s *Service) validatePrivateRequirements() error {
	if !s.Private {
		return nil
	}
	if s.Mode != "" && s.Mode != ModeHTTP {
		return fmt.Errorf("private services only support HTTP mode, got %q", s.Mode)
	}
	if len(s.AccessGroups) == 0 {
		return errors.New("private services require at least one access group")
	}
	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 {

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Attach at least one existing access group to the service (via its update API) together with setting Private.
  2. If no group exists yet, create the group first, then mark the service private.
  3. If you did not intend NetBird-only access, leave Private unset (public/SSO path).

Example fix

# before
{"name": "internal-api", "private": true, "targets": ["http://10.0.0.5:8080"]}

# after
{"name": "internal-api", "private": true, "access_groups": ["grp-ops"], "targets": ["http://10.0.0.5:8080"]}
Defensive patterns

Strategy: validation

Validate before calling

if svc.Private && len(svc.AccessGroups) == 0 {
    return fmt.Errorf("private services need at least one access group")
}

Prevention

When it happens

Trigger: Creating/updating a service with Private: true and an empty or missing AccessGroups list.

Common situations: 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.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/2ee0b73d424e6510. Report an issue: GitHub.