netbirdio/netbird · error

engine not started

Error message

engine not started

What it means

Returned by the daemon's GetPeerSSHHostKey RPC handler (client/server/server.go:1635) when the connect client exists but its Engine() returns nil. The connect client outlives the engine: during connection setup, reconnect cycles, or after the engine stopped, the client object is present while the engine is not, so peer SSH host-key lookup has no data source.

Source

Thrown at client/server/server.go:1635

	ctx context.Context,
	req *proto.GetPeerSSHHostKeyRequest,
) (*proto.GetPeerSSHHostKeyResponse, error) {
	if ctx.Err() != nil {
		return nil, ctx.Err()
	}

	s.mutex.Lock()
	connectClient := s.connectClient
	statusRecorder := s.statusRecorder
	s.mutex.Unlock()

	if connectClient == nil {
		return nil, errors.New("client not initialized")
	}

	engine := connectClient.Engine()
	if engine == nil {
		return nil, errors.New("engine not started")
	}

	peerAddress := req.GetPeerAddress()
	hostKey, found := engine.GetPeerSSHKey(peerAddress)

	response := &proto.GetPeerSSHHostKeyResponse{
		Found: found,
	}

	if !found {
		return response, nil
	}

	response.SshHostKey = hostKey

	if statusRecorder == nil {
		return response, nil
	}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Retry after the daemon reports Connected (the engine is set once the connection is established).
  2. In scripts, add a short bounded retry loop around the SSH host-key lookup.
  3. If it persists while Connected, check daemon logs for an engine start failure (interface creation, routing setup).

Example fix

// before: single shot during connecting window
resp, err := daemonClient.GetPeerSSHHostKey(ctx, req)

// after: bounded retry around the transient window
var resp *daemonpb.GetPeerSSHHostKeyResponse
for i := 0; i < 5; i++ {
    resp, err = daemonClient.GetPeerSSHHostKey(ctx, req)
    if err == nil || !strings.Contains(err.Error(), "engine not started") {
        break
    }
    time.Sleep(500 * time.Millisecond)
}
Defensive patterns

Strategy: validation

Validate before calling

// Engine exists only once connected; check before the lookup.
status, err := daemonClient.Status(ctx)
if err != nil {
    return err
}
if status.GetStatus() != daemonpb.StatusEnum_CONNECTED {
    return fmt.Errorf("engine not ready (status %s); retry shortly", status.GetStatus())
}

Try / catch

var resp *daemonpb.GetPeerSSHHostKeyResponse
err := retryOnMessage(ctx, 5, 500*time.Millisecond,
    "engine not started",
    func() (*daemonpb.GetPeerSSHHostKeyResponse, error) {
        return daemonClient.GetPeerSSHHostKey(ctx, req)
    })

Prevention

When it happens

Trigger: GetPeerSSHHostKey called while the daemon is between engine start and engine ready (connecting state), during a management-triggered reconnect that nils the engine, or right after the engine was stopped but before the whole client was torn down.

Common situations: SSH into a peer immediately after daemon start while management login is still in flight; transient window during network-map resync or engine rebuild; UI querying SSH keys during down/logout teardown.

Related errors


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