netbirdio/netbird · error

auth is not supported for TCP/UDP services

Error message

auth is not supported for TCP/UDP services

What it means

Returned by validateTCPUDPMode when isAuthEnabled() is true: any of password_auth, pin_auth, bearer_auth, or an entry in header_auths with enabled=true triggers it. TCP/UDP modes are byte-stream/datagram passthrough and cannot present an HTTP auth challenge, so the combination is rejected outright rather than silently ignored.

Source

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

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

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 {

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Remove or set enabled=false on all auth blocks (password_auth, pin_auth, bearer_auth, header_auths) for the tcp/udp service.
  2. If callers genuinely need a login screen, keep the service in http mode where auth is supported.
  3. Restrict access at the network layer instead: NetBird access groups, ACLs, or access_restrictions (allowed_cidrs).
  4. Audit the full auth object, not just one sub-block - isAuthEnabled() checks every auth type.

Example fix

// before
{
  "mode": "tcp",
  "listen_port": 3306,
  "auth": { "password_auth": { "enabled": true, "password": "secret" } },
  "targets": [...]
}

// after
{
  "mode": "tcp",
  "listen_port": 3306,
  "targets": [...]
}
Defensive patterns

Strategy: validation

Validate before calling

func anyAuthEnabled(a AuthConfig) bool {
	if (a.PasswordAuth != nil && a.PasswordAuth.Enabled) ||
		(a.PinAuth != nil && a.PinAuth.Enabled) ||
		(a.BearerAuth != nil && a.BearerAuth.Enabled) {
		return true
	}
	for _, h := range a.HeaderAuths {
		if h != nil && h.Enabled {
			return true
		}
	}
	return false
}

func checkL4NoAuth(mode string, auth AuthConfig) error {
	if (mode == "tcp" || mode == "udp") && anyAuthEnabled(auth) {
		return errors.New("strip all auth blocks for tcp/udp services")
	}
	return nil
}

Type guard

func isL4AuthClean(mode string, auth AuthConfig) bool {
	if mode != "tcp" && mode != "udp" {
		return true
	}
	return !anyAuthEnabled(auth)
}

Try / catch

if err := svc.Validate(); err != nil {
	if strings.Contains(err.Error(), "auth is not supported for TCP/UDP") {
		return respondBadRequest(errors.New("remove auth or switch to http mode"))
	}
	return respondBadRequest(err)
}

Prevention

When it happens

Trigger: Creating a tcp/udp service whose payload still carries auth.password_auth.enabled=true, auth.pin_auth, auth.bearer_auth, or auth.header_auths[*].enabled=true; switching a previously HTTP service with basic auth to mode tcp without stripping the auth block.

Common situations: Cloning an HTTP service JSON (which had basic auth) for a new database TCP forward. A UI auth tab left enabled when the mode dropdown changed to TCP. A version upgrade where auth fields on L4 services were previously ignored and are now validated, breaking old automation payloads.

Related errors


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