netbirdio/netbird · error

expose service: %v

Error message

expose service: %v

What it means

The initial client.ExposeService RPC was rejected by the daemon; the gRPC status message is surfaced. By this point every flag passed validateExposeFlags, the daemon connection was live, and the request (port, protocol enum, pin/password/user-groups, domain, name prefix, and ListenPort for L4) had been sent — so the failure is a daemon-side or management-side refusal, not a CLI validation issue.

Source

Thrown at client/cmd/expose.go:205

		return err
	}

	req := &proto.ExposeServiceRequest{
		Port:       uint32(port),
		Protocol:   protocol,
		Pin:        exposePin,
		Password:   exposePassword,
		UserGroups: exposeUserGroups,
		Domain:     exposeDomain,
		NamePrefix: exposeNamePrefix,
	}
	if isClusterProtocol(exposeProtocol) {
		req.ListenPort = uint32(resolveExternalPort(port))
	}

	stream, err := client.ExposeService(ctx, req)
	if err != nil {
		return fmt.Errorf("expose service: %v", status.Convert(err).Message())
	}

	if err := handleExposeReady(cmd, stream, port); err != nil {
		return err
	}

	return waitForExposeEvents(cmd, ctx, stream)
}

func toExposeProtocol(exposeProtocol string) (proto.ExposeProtocol, error) {
	p, err := expose.ParseProtocolType(exposeProtocol)
	if err != nil {
		return 0, fmt.Errorf("invalid protocol: %w", err)
	}

	switch p {
	case expose.ProtocolHTTP:
		return proto.ExposeProtocol_EXPOSE_HTTP, nil

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Make sure the peer is connected first: `netbird status` must show Connected; run `netbird up` and wait, then retry
  2. Confirm your management/account supports exposed services (cloud feature flag or self-hosted management version) and that it is enabled for the account
  3. If using --with-custom-domain, verify the domain is added and verified for your account in the dashboard, or drop the flag to get an assigned domain
  4. Re-run with the daemon at debug log level and read its log at the failure timestamp for the exact refusal reason
Defensive patterns

Strategy: try-catch

Validate before calling

# require a healthy, connected peer before exposing
netbird status --check live || { echo 'peer not connected; run netbird up first'; exit 1; }

Try / catch

stream, err := client.ExposeService(ctx, req)
if err != nil {
	msg := status.Convert(err).Message()
	switch status.Code(err) {
	case codes.FailedPrecondition:
		// peer not connected/logged in: bring it up, then retry
		return fmt.Errorf("expose service: connect the peer first (netbird up): %s", msg)
	case codes.InvalidArgument:
		// server-side validation (domain, name prefix): fix the flags
		return fmt.Errorf("expose service: invalid request (%s); check --with-custom-domain/--with-name-prefix", msg)
	default:
		return fmt.Errorf("expose service: %s", msg)
	}
}

Prevention

When it happens

Trigger: The peer is not connected or not logged in, so the daemon refuses to create an exposed service; the management plane does not support the services feature; a --with-custom-domain value that is not configured on the account; server-side validation rejecting name prefix or domain; the expose subsystem on the daemon not being ready; the context canceled before the RPC was sent.

Common situations: Running `netbird expose` before `netbird up` has finished connecting; self-hosted management older than the services feature; assuming any domain string works with --with-custom-domain; running against a management account where the reverse-proxy feature is disabled.

Related errors


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