netbirdio/netbird · error
service domain is required
Error message
service domain is required
What it means
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.
Source
Thrown at management/internals/modules/reverseproxy/service/service.go:912
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 {
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 {View on GitHub (pinned to 93e97f4bf1)
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.
Example fix
# before
{"name": "internal-api", "mode": "http", "targets": ["http://10.0.0.5:8080"]}
# after
{"name": "internal-api", "mode": "http", "domain": "api.example.net", "targets": ["http://10.0.0.5:8080"]} Defensive patterns
Strategy: validation
Validate before calling
mode := svc.Mode
if mode == "" {
mode = ModeHTTP
}
if mode == ModeHTTP && svc.Domain == "" {
return fmt.Errorf("domain is required for HTTP services")
} Prevention
- 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.
When it happens
Trigger: Creating/updating a service with mode http (or omitted) and no Domain field; templates that only set targets and name.
Common situations: 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.
Related errors
- service name is required
- service name exceeds maximum length of 255 characters
- at least one target is required
- private services require at least one access group
- private services cannot enable bearer auth (SSO): NetBird-on
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/d3799cd4a31d3aca.
Report an issue: GitHub.