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

  1. Run `headscale nodes list` and re-check the identifier you are passing
  2. If the node was deleted, re-register it with a fresh auth-key
  3. 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

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


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/37f25a0cf227e03b. Report an issue: GitHub.