juanfont/headscale · error · ErrRegistrationExpired

registration expired

Error message

registration expired

What it means

ErrRegistrationExpired is a sentinel error in hscontrol/state/state.go:114 signalling that a node registration attempt timed out before an auth verdict arrived. The register cache resolves entries that time out or are evicted with this error (state.go:221 rn.FinishAuth(types.AuthVerdict{Err: ErrRegistrationExpired}); auth_cache_test.go confirms evicted entries surface it). API layers map it to a register-failed response (v1/errors.go:27).

Source

Thrown at hscontrol/state/state.go:114

	"NodeKey",
	"DiscoKey",
	"Endpoints",
	"Hostinfo",
	"IPv4",
	"IPv6",
	"Hostname",
	"GivenName",
	"UserID",
	"RegisterMethod",
	"Tags",
	"Expiry",
	"LastSeen",
	"ApprovedRoutes",
	"UpdatedAt",
}

// ErrRegistrationExpired is returned when a registration has expired.
var ErrRegistrationExpired = errors.New("registration expired")

// ErrNodeKeyInUse is returned when a registration or re-auth claims a NodeKey
// already bound to a different machine, enforcing the 1:1 NodeKey<->MachineKey
// binding.
var ErrNodeKeyInUse = errors.New("node key already in use by another machine")

// ErrAmbiguousNodeOwnership is returned when a machine key maps to a set of
// nodes from which the correct one to update or convert cannot be determined:
// multiple user-owned candidates for a tagged conversion, or a tagged node and
// a user-owned node coexisting (impossible per validateNodeOwnership). The
// registration is rejected rather than mutating an arbitrarily-picked node.
var ErrAmbiguousNodeOwnership = errors.New("machine key maps to ambiguous node ownership")

// sshCheckPair identifies a (source, destination) node pair for
// SSH check auth tracking.
type sshCheckPair struct {
	Src types.NodeID
	Dst types.NodeID

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Re-run 'tailscale up/login' on the node to start a fresh registration
  2. Complete the auth flow promptly after the registration URL is issued
  3. Increase tuning.register_cache_expiration if auth legitimately takes longer (e.g. slow MFA)
  4. Increase tuning.register_cache_max_entries under registration bursts

Example fix

null
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

null

Try / catch

if errors.Is(err, state.ErrRegistrationExpired) {
    // safe to retry: run tailscale up/login again for a fresh registration URL
}

Prevention

When it happens

Trigger: A node starts registration (interactive URL or pre-auth key flow) but no verdict arrives within tuning.register_cache_expiration; the cache entry is evicted (max entries exceeded) before the user completes auth; the OIDC callback or key validation never lands.

Common situations: User opens the registration URL but completes login after the timeout; heavy registration bursts exceeding register_cache_max_entries (default 1024) evicting pending entries; slow IdP or unreachable callback during OIDC registration.

Related errors


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