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
- Retry after the daemon reports Connected (the engine is set once the connection is established).
- In scripts, add a short bounded retry loop around the SSH host-key lookup.
- 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
- Treat 'engine not started' as a transient connecting-window state: bounded retry.
- Watch SubscribeStatus events to learn when the engine is up instead of polling blindly.
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
- client not initialized
- service is not up
- management client is not initialised
- peer has no stored SSH host key
- create SSH proxy: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/e609a5297f52ecb7.
Report an issue: GitHub.