netbirdio/netbird · error

listen_port is not supported for HTTP services

Error message

listen_port is not supported for HTTP services

What it means

Returned by Service.Validate() -> validateHTTPMode when the service mode resolves to "http" (an empty Mode is defaulted to http by Validate()) and Service.ListenPort is non-zero. HTTP reverse-proxy services are routed by hostname on the proxy's shared listeners, so a dedicated listen port has no meaning there; listen ports belong to the L4 modes (required for tls, optional-but-routed for tcp/udp). The check fails fast at API/store time so the invalid combination never reaches the proxy config push.

Source

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

	}
	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 {
		return errors.New("proxy_protocol is not supported for UDP services")
	}
	return s.validateL4Target(s.Targets[0])

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Remove listen_port (omit the field or set it to 0) from the HTTP service definition.
  2. If you actually want port-based routing, change mode to "tcp" or "udp" and keep the port.
  3. If you are terminating TLS on a dedicated port with SNI routing, use mode "tls" which requires listen_port.
  4. If a UI/automation always sends the field, patch it to omit listen_port when mode is http.

Example fix

// before
{
  "name": "my-app",
  "mode": "http",
  "domain": "netbird.example.com",
  "listen_port": 8443,
  "targets": [...]
}

// after
{
  "name": "my-app",
  "mode": "http",
  "domain": "netbird.example.com",
  "targets": [...]
}
Defensive patterns

Strategy: validation

Validate before calling

func checkHTTPListenPort(mode string, listenPort uint16) error {
	if mode == "" {
		mode = "http"
	}
	if mode == "http" && listenPort != 0 {
		return fmt.Errorf("listen_port %d not allowed for http service", listenPort)
	}
	return nil
}

Type guard

func isHTTPModeSafe(mode string, listenPort uint16) bool {
	if mode == "" {
		mode = "http"
	}
	return mode != "http" || listenPort == 0
}

Try / catch

if err := svc.Validate(); err != nil {
	if strings.Contains(err.Error(), "listen_port is not supported for HTTP") {
		// config bug, fix payload - never retry
	}
	return respondBadRequest(err)
}

Prevention

When it happens

Trigger: POST/PUT of a reverse-proxy service with mode "http" or mode omitted and a non-zero listen_port in the payload; updating an existing TCP service to mode http without clearing listen_port; a UI or Terraform-like module that always serializes a listen_port field (e.g. 443) even for HTTP services.

Common situations: Copying a TCP/TLS service JSON as a template for a new HTTP service and forgetting to delete listen_port. Frontend forms that submit every field with a default port value. Migrating a service between modes during a restructure and leaving stale fields behind.

Related errors


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