netbirdio/netbird · error
expose manager not available
Error message
expose manager not available
What it means
Returned by Client.Expose when engine.GetExposeManager() returns nil. The expose manager is only created during Engine.Start (client/internal/engine.go:545), so a nil manager means the engine has not completed its start phase under this engine instance. getEngine already rejected not-started clients, so in practice this surfaces when the engine exists but its start path that installs the expose manager has not run.
Source
Thrown at client/embed/embed.go:433
DialContext: c.Dial,
}
return &http.Client{
Transport: transport,
}
}
// Expose exposes a local service via the NetBird reverse proxy, making it accessible through a public URL.
// It returns an ExposeSession. Call Wait on the session to keep it alive.
func (c *Client) Expose(ctx context.Context, req ExposeRequest) (*ExposeSession, error) {
engine, err := c.getEngine()
if err != nil {
return nil, err
}
mgr := engine.GetExposeManager()
if mgr == nil {
return nil, fmt.Errorf("expose manager not available")
}
resp, err := mgr.Expose(ctx, req)
if err != nil {
return nil, fmt.Errorf("expose: %w", err)
}
return &ExposeSession{
Domain: resp.Domain,
ServiceName: resp.ServiceName,
ServiceURL: resp.ServiceURL,
mgr: mgr,
}, nil
}
// IdentityForIP looks up a remote peer by its tunnel IP using the
// embedded client's status recorder. Returns the peer's WireGuard public
// key and FQDN. ok=false means the IP doesn't belong to an active peerView on GitHub (pinned to 93e97f4bf1)
Solutions
- Only call Expose after Start has returned nil, sequentially.
- If the error persists after a successful Start, restart the client to get a cleanly started engine.
- Guard the call site: check client.Status() or your own started flag before Expose.
Example fix
// before
go func() { _ = client.Start(ctx) }()
sess, err := client.Expose(ctx, req) // races engine start
// after
if err := client.Start(ctx); err != nil { return err }
sess, err := client.Expose(ctx, req) Defensive patterns
Strategy: type-guard
Type guard
func canExpose(c *embed.Client) error {
// getEngine-equivalent guard: client must be started and engine up
if _, err := c.Status(); err != nil {
return fmt.Errorf("client not ready: %w", err)
}
return nil
} Try / catch
sess, err := client.Expose(ctx, req)
if err != nil {
if strings.Contains(err.Error(), "expose manager not available") {
// engine not started: call Start first, then retry once
}
} Prevention
- Sequence Expose strictly after a successful Start in the same goroutine or via a ready gate.
- Do not share Expose calls across Client instances; only the started one has a manager.
When it happens
Trigger: Calling Expose on a Client whose engine object exists but is still initializing, or on an engine whose start failed/was aborted before the expose manager was created.
Common situations: Racing Expose against a Start call that has not returned yet; calling Expose on a second Client instance that was never started while assuming state is shared; start failure leaving a half-constructed engine reachable.
Related errors
- expose: %w
- stop error after context done. Stop error: %w. Context done:
- stop: %w
- host argument required
- invalid user@host format
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/38d4d15a32f4d2bd.
Report an issue: GitHub.