netbirdio/netbird · error

listen_port is required for TLS services

Error message

listen_port is required for TLS services

What it means

Returned by validateTLSMode when a tls service has ListenPort == 0. Unlike HTTP (shared hostname-routed listeners) and unlike tcp/udp (where the port allocation is implied by the mode), a TLS service must declare the dedicated port it listens on so the proxy can accept raw TLS and dispatch by SNI. It is the inverse of error 40, which rejects a listen_port on http services.

Source

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

	}
	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])
}

func (s *Service) validateTLSMode() error {
	if s.Domain == "" {
		return errors.New("domain is required for TLS services (used for SNI matching)")
	}
	if s.isAuthEnabled() {
		return errors.New("auth is not supported for TLS services")
	}
	if s.ListenPort == 0 {
		return errors.New("listen_port is required for TLS services")
	}
	if len(s.Targets) != 1 {
		return errors.New("TLS services must have exactly one target")
	}
	return s.validateL4Target(s.Targets[0])
}

func (s *Service) validateHTTPTargets() error {
	for i, target := range s.Targets {
		switch target.TargetType {
		case TargetTypePeer, TargetTypeHost, TargetTypeDomain:
			// Host is normally overwritten by replaceHostByLookup with the
			// resolved peer IP / resource address; operator-supplied values
			// are honored only when DirectUpstream is set. Validate the
			// override here so misconfigured hosts fail fast at API time.
			if err := validateDirectUpstreamHost(i, target); err != nil {
				return err
			}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Set listen_port to the port the proxy should accept TLS on, e.g. 4433 or 853.
  2. Double-check you set listen_port (service-level listener), not target.port (upstream port).
  3. If you wanted hostname routing without a dedicated port, use mode http with an https target_protocol instead.

Example fix

// before
{ "mode": "tls", "domain": "db.netbird.example.com",
  "targets": [ { "target_id": "peer-a", "port": 5432 } ] }

// after
{ "mode": "tls", "domain": "db.netbird.example.com", "listen_port": 4433,
  "targets": [ { "target_id": "peer-a", "port": 5432 } ] }
Defensive patterns

Strategy: validation

Validate before calling

func checkTLSListenPort(mode string, listenPort uint16) error {
	if mode == "tls" && listenPort == 0 {
		return errors.New("listen_port is required for tls services")
	}
	return nil
}

Type guard

func hasTLSListenPort(mode string, listenPort uint16) bool {
	return mode != "tls" || listenPort != 0
}

Try / catch

if err := svc.Validate(); err != nil {
	if strings.Contains(err.Error(), "listen_port is required for TLS") {
		return respondBadRequest(errors.New("set an explicit proxy listener port, e.g. 4433"))
	}
	return respondBadRequest(err)
}

Prevention

When it happens

Trigger: Creating a tls service with no listen_port in the payload; an API client that maps its generic 'port' field to the target port but never to listen_port; switching mode from http to tls on a service that legitimately had no listen port.

Common situations: Assuming TLS shares the standard 443 listener via SNI so no port is needed - this implementation requires an explicit dedicated port. Confusion between target.port (upstream) and listen_port (proxy listener). In the peer-expose request the fields are Port and ListenPort, and only ListenPort overrides the listener for L4.

Understand the failure class

Related errors


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