netbirdio/netbird · warning
peer has no stored SSH host key
Error message
peer has no stored SSH host key
What it means
Sentinel ErrNoStoredKey (client/ssh/common.go:30) from the shared SSH package. The NetBird daemon learns each peer's SSH host key from management and stores it; the HostKeyVerifier used by the ssh client returns this sentinel when the peer is known in the overlay but no host key has been stored for it yet, so strict host verification cannot proceed.
Source
Thrown at client/ssh/common.go:30
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
"github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/util"
)
const (
NetBirdSSHConfigFile = "99-netbird.conf"
UnixSSHConfigDir = "/etc/ssh/ssh_config.d"
WindowsSSHConfigDir = "ssh/ssh_config.d"
)
var (
// ErrPeerNotFound indicates the peer was not found in the network
ErrPeerNotFound = errors.New("peer not found in network")
// ErrNoStoredKey indicates the peer has no stored SSH host key
ErrNoStoredKey = errors.New("peer has no stored SSH host key")
)
// HostKeyVerifier provides SSH host key verification
type HostKeyVerifier interface {
VerifySSHHostKey(peerAddress string, key []byte) error
}
// DaemonHostKeyVerifier implements HostKeyVerifier using the NetBird daemon
type DaemonHostKeyVerifier struct {
client proto.DaemonServiceClient
}
// NewDaemonHostKeyVerifier creates a new daemon-based host key verifier
func NewDaemonHostKeyVerifier(client proto.DaemonServiceClient) *DaemonHostKeyVerifier {
return &DaemonHostKeyVerifier{
client: client,
}
}View on GitHub (pinned to 93e97f4bf1)
Solutions
- Enable the NetBird SSH server on the target peer (management setting / peer config) so its host key is published.
- Wait for both peers to be Connected and the network map to sync, then retry.
- In interactive clients, distinguish this sentinel (ErrPeerNotFound vs ErrNoStoredKey) and prompt the user instead of failing silently; avoid falling back to accepting an unverified key automatically.
Example fix
// before: any verification error aborts with a generic message
if err := verifier.VerifySSHHostKey(addr, key); err != nil {
return fmt.Errorf("ssh host key verification failed: %w", err)
}
// after: distinguish 'no key stored' and guide the user
if err := verifier.VerifySSHHostKey(addr, key); err != nil {
if errors.Is(err, ssh.ErrNoStoredKey) {
return fmt.Errorf("no stored host key for %s; is the peer's SSH server enabled?", addr)
}
return err
} Defensive patterns
Strategy: try-catch
Validate before calling
// Ask the daemon first whether a key exists before strict verification.
resp, err := daemonClient.GetPeerSSHHostKey(ctx, &daemonpb.GetPeerSSHHostKeyRequest{PeerAddress: addr})
if err == nil && !resp.GetFound() {
return fmt.Errorf("no stored host key for %s; enable SSH on the peer or wait for sync", addr)
} Type guard
func isNoStoredKey(err error) bool {
return errors.Is(err, ssh.ErrNoStoredKey)
} Try / catch
if err := verifier.VerifySSHHostKey(addr, key); err != nil {
if ssh.IsNoStoredKey(err) {
// trust not yet established: prompt the user or wait for network-map sync
return fmt.Errorf("no stored key for peer %s", addr)
}
return err // genuine key mismatch: abort
} Prevention
- Distinguish ErrNoStoredKey from a key mismatch: one is 'unknown', the other is 'suspected MITM'.
- Never auto-accept an unverifiable key when none is stored.
- Ensure peers run with the SSH server feature enabled so keys get distributed.
When it happens
Trigger: DaemonHostKeyVerifier.VerifySSHHostKey is called for a peer whose key the engine never received: SSH server feature not enabled on the remote peer, the peer's key not yet distributed via the network map, or lookup done before the engine synced (the underlying GetPeerSSHHostKey path returning Found=false).
Common situations: First SSH attempt to a peer whose owner has not enabled netbird's SSH server; connecting right after either side connected before the network map converged; environments where management disables SSH key distribution.
Related errors
- client not initialized
- engine not started
- management client is not initialised
- profile not found
- watcher closed unexpectedly
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/08b627fa2d752139.
Report an issue: GitHub.