juanfont/headscale · warning

route is not available on node

Error message

route is not available on node

What it means

Sentinel in hscontrol/db/node.go returned by EnableRoute/DisableRoute-style helpers when asked to toggle a route that the node does not currently advertise. Routes must appear in the node's Hostinfo advertised routes before they can be enabled or disabled as approved subnet routes.

Source

Thrown at hscontrol/db/node.go:45

	// 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) {
	nodes := types.Nodes{}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Run `headscale routes list` to see routes actually advertised, and enable by the exact prefix/ID shown
  2. On the node, re-run `tailscale set --advertise-routes=<prefix>` so the route is advertised, wait for it to appear, then enable
  3. Fix automation scripts to look up route IDs dynamically instead of hardcoding them

Example fix

# before (route not advertised by node)
headscale routes enable --route 5

# after
# on the node:
tailscale set --advertise-routes=10.42.0.0/24
# on the server:
headscale routes list
headscale routes enable --route <id-from-list>
Defensive patterns

Strategy: validation

Validate before calling

// confirm the node advertises the prefix before enabling it
node, _ := hsdb.GetNodeByID(nodeID)
for _, r := range node.Hostinfo().RoutableIPs {
    if r == wantedPrefix { /* safe to enable */ }
}

Try / catch

if err := hsdb.EnableRoute(tx, nodeID, routeID); err != nil {
    if errors.Is(err, db.ErrNodeRouteIsNotAvailable) {
        return fmt.Errorf("node %d does not advertise this route; run `tailscale set --advertise-routes=%s` first", nodeID, prefix)
    }
    return err
}

Prevention

When it happens

Trigger: Calling `headscale routes enable` with a route/prefix the node has never advertised via `tailscale up --advertise-routes=...`; enabling by route ID that belongs to another node; node changed its advertised routes so the old prefix no longer exists.

Common situations: Approving a route typo'd in the CLI; the node dropped the advertisement (rebooted without --advertise-routes) and an automation still tries to approve the old prefix.

Related errors


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