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

  1. Check logs for the wrapped non-HTTP cause — usually a database error — and fix that first (locks, connectivity, pool size).
  2. Retry `tailscale up --authkey=...` after the DB issue clears; registration with the same key is safe unless it was single-use and already consumed.
  3. If the error is actually an HTTPError (expired/used key), create a new auth key: `headscale preauthkeys create ...` and retry.
  4. 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

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


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