juanfont/headscale · error
handling register with auth key: %w
Error message
handling register with auth key: %w
What it means
Wraps failures of handleRegisterWithAuthKey during registration with a pre-auth key (hscontrol/auth.go:141). Note the special passthrough: if the inner error is an HTTPError it is returned unwrapped so the HTTP layer maps it to a status code; this generic wrap only applies to non-HTTP errors such as state/DB failures. Business-rule failures (invalid, expired, over-used, or reusable-misused auth keys) surface as HTTPError instead.
Source
Thrown at hscontrol/auth.go:141
// If the [tailcfg.RegisterRequest] has a Followup URL, it means that the
// node has already started the registration process and we should wait for
// it to finish the original registration.
if req.Followup != "" {
return h.waitForFollowup(ctx, req, machineKey)
}
// Pre authenticated keys are handled slightly different than interactive
// logins as they can be done fully sync and we can respond to the node with
// the result as it is waiting.
if isAuthKey(req) {
resp, err := h.handleRegisterWithAuthKey(req, machineKey)
if err != nil {
// Preserve HTTPError types so they can be handled properly by the HTTP layer
if httpErr, ok := errors.AsType[HTTPError](err); ok {
return nil, httpErr
}
return nil, fmt.Errorf("handling register with auth key: %w", err)
}
return resp, nil
}
resp, err := h.handleRegisterInteractive(req, machineKey)
if err != nil {
return nil, fmt.Errorf("handling register interactive: %w", err)
}
return resp, nil
}
// handleLogout checks if the [tailcfg.RegisterRequest] is a
// logout attempt from a node. If the node is not attempting to.
func (h *Headscale) handleLogout(
node types.NodeView,
req tailcfg.RegisterRequest,View on GitHub (pinned to 565fd254d0)
Solutions
- Check logs for the wrapped non-HTTP cause — usually a database error — and fix that first (locks, connectivity, pool size).
- Retry `tailscale up --authkey=...` after the DB issue clears; registration with the same key is safe unless it was single-use and already consumed.
- If the error is actually an HTTPError (expired/used key), create a new auth key: `headscale preauthkeys create ...` and retry.
- For bulk parallel enrollments, stagger registrations or move to PostgreSQL to avoid SQLite write locking.
Defensive patterns
Strategy: try-catch
Type guard
// HTTPError is passed through unwrapped; use it to distinguish key problems from DB problems.
if httpErr, ok := errors.AsType[HTTPError](err); ok {
// auth-key policy failure (expired/used/invalid): surface to user, create a new key
} else {
// wrapped 'handling register with auth key': infrastructure/DB failure, retry
} Try / catch
resp, err := h.handleRegister(req, mk)
if err != nil {
if httpErr, ok := errors.AsType[HTTPError](err); ok {
return nil, httpErr // maps to an HTTP status for the client
}
// retryable infra failure: log, back off, retry registration
} Prevention
- Check auth-key validity (expiry, use count) before mass enrollment.
- Stagger automated registrations to avoid SQLite write storms.
- Distinguish HTTPError (user-fixable) from wrapped errors (infra-fixable) in automation.
When it happens
Trigger: `tailscale up --authkey=<key>` while the DB write of the new node fails (lock, connection loss); state-layer errors while attaching the node to the key's user; policy evaluation errors during pre-auth registration. Distinct from the HTTPError cases: malformed key string, expired key, single-use key already consumed, or user mismatch.
Common situations: Automated node provisioning (Terraform/Ansible/containers) hitting DB contention when many nodes register in parallel; DB briefly unavailable during bulk enrollment; ephemeral test nodes registering faster than SQLite can commit.
Related errors
- handling logout: %w
- handling existing node: %w
- handling register interactive: %w
- auth-key expired
- auth-key has already been used
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/362ec11d28260de1.
Report an issue: GitHub.