juanfont/headscale · warning
node not found
Error message
node not found
What it means
Generic not-found sentinel in hscontrol/db/node.go returned by node lookup helpers (GetNode, and registration paths) when no row matches the requested node ID or machine key. It typically wraps or is wrapped with gorm.ErrRecordNotFound context at call sites.
Source
Thrown at hscontrol/db/node.go:44
// defaultTestNodePrefix is the default hostname prefix for nodes created in tests.
defaultTestNodePrefix = "testnode"
)
// ErrNodeNameNotUnique is returned when a node name is not unique.
var ErrNodeNameNotUnique = errors.New("node name is not unique")
// preloadNode returns a session that eager-loads a node's AuthKey, the
// AuthKey's User, and the node's User.
func preloadNode(tx *gorm.DB) *gorm.DB {
return tx.
Preload("AuthKey").
Preload("AuthKey.User").
Preload("User")
}
var (
ErrNodeNotFound = errors.New("node not found")
ErrNodeRouteIsNotAvailable = errors.New("route is not available on node")
ErrNodeNotFoundRegistrationCache = errors.New(
"node not found in registration cache",
)
ErrCouldNotConvertNodeInterface = errors.New("failed to convert node interface")
)
// ListPeers returns peers of node, regardless of any Policy or if the node is expired.
// If no peer IDs are given, all peers are returned.
// If at least one peer ID is given, only these peer nodes will be returned.
func (hsdb *HSDatabase) ListPeers(nodeID types.NodeID, peerIDs ...types.NodeID) (types.Nodes, error) {
return ListPeers(hsdb.DB, nodeID, peerIDs...)
}
// ListPeers returns peers of node, regardless of any Policy or if the node is expired.
// If no peer IDs are given, all peers are returned.
// If at least one peer ID is given, only these peer nodes will be returned.
func ListPeers(tx *gorm.DB, nodeID types.NodeID, peerIDs ...types.NodeID) (types.Nodes, error) {View on GitHub (pinned to 565fd254d0)
Solutions
- Run `headscale nodes list` and re-check the identifier you are passing
- If the node was deleted, re-register it with a fresh auth-key
- In code, check errors.Is(err, db.ErrNodeNotFound) and treat it as a 404 path rather than a server error
Defensive patterns
Strategy: try-catch
Try / catch
node, err := hsdb.GetNodeByID(nodeID)
if err != nil {
if errors.Is(err, db.ErrNodeNotFound) {
return http.NotFoundHandler().ServeHTTP // 404, not 500
}
return err
} Prevention
- Treat node IDs as ephemeral: look them up by name/machine key when possible
- Handle 404 explicitly in API clients and refresh cached IDs
- Avoid racing deletions with reads by serializing admin operations
When it happens
Trigger: Calling GetNode/GetNodeByMachineKey/GetNodeByAnyKey with an ID that does not exist (deleted node, typo'd ID), or a registration cache miss during node re-registration (distinct sibling error ErrNodeNotFoundRegistrationCache).
Common situations: CLI command with a stale node ID after the node was deleted; API scripts holding cached IDs across node deletion; race between deletion and a concurrent query.
Related errors
- failed to convert node interface
- node not found
- failed to parse ApiKey
- failed to generate API key
- database type not supported
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/37f25a0cf227e03b.
Report an issue: GitHub.