juanfont/headscale · error

failed to parse auth-key

Error message

failed to parse auth-key

What it means

Sentinel in hscontrol/db/preauth_keys.go returned by findAuthKey (preauth_keys.go:183+) when the presented auth-key string is structurally invalid: empty, missing the authKeyPrefix marker, or with prefix/secret segments of the wrong shape. It fires before any database lookup, distinguishing format errors from 'key not found'. A sibling error ErrPreAuthKeyNotTaggedOrOwned covers ownership validation.

Source

Thrown at hscontrol/db/preauth_keys.go:183

	}

	return keys, nil
}

// ListPreAuthKeysByUser returns all [types.PreAuthKey] values belonging to a specific user.
func ListPreAuthKeysByUser(tx *gorm.DB, uid types.UserID) ([]types.PreAuthKey, error) {
	var keys []types.PreAuthKey

	err := tx.Preload("User").Where("user_id = ?", uint(uid)).Find(&keys).Error
	if err != nil {
		return nil, err
	}

	return keys, nil
}

var (
	ErrPreAuthKeyFailedToParse    = errors.New("failed to parse auth-key")
	ErrPreAuthKeyNotTaggedOrOwned = errors.New("auth-key must be either tagged or owned by user")
)

func findAuthKey(tx *gorm.DB, keyStr string) (*types.PreAuthKey, error) {
	var pak types.PreAuthKey

	// Validate input is not empty
	if keyStr == "" {
		return nil, ErrPreAuthKeyFailedToParse
	}

	_, prefixAndHash, found := strings.Cut(keyStr, authKeyPrefix)

	if !found {
		// Legacy format (plaintext) - backwards compatibility
		err := tx.Preload("User").First(&pak, "key = ?", keyStr).Error
		if err != nil {
			return nil, ErrPreAuthKeyNotFound

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Re-copy the full authkey from `headscale preauthkeys create` output — it must be one unbroken string
  2. Check the variable holding the key is set and unmodified in your provisioning script (print its length, not the value)
  3. Confirm you are using an auth-key, not an admin API key or oauth token

Example fix

# before
key="$AUTH_KEY"   # AUTH_KEY unset -> empty string
tailscale up --auth-key="$key"

# after
: "${AUTH_KEY:?AUTH_KEY must be set}"
tailscale up --auth-key="$AUTH_KEY"
Defensive patterns

Strategy: type-guard

Validate before calling

// fail fast in provisioning scripts before calling tailscale
if [ -z "$AUTH_KEY" ]; then
    echo "AUTH_KEY is empty" >&2; exit 1
fi
case "$AUTH_KEY" in
    authkey-*) ;;
    *) echo "not an auth-key" >&2; exit 1 ;;
esac

Type guard

func isPreAuthKey(s string) bool {
    if s == "" {
        return false
    }
    // must contain the auth-key prefix marker with non-empty remainder
    _, rest, found := strings.Cut(s, authKeyPrefix)
    return found && len(rest) > 0
}

Try / catch

pak, err := db.FindPreAuthKey(keyStr)
if err != nil {
    if errors.Is(err, db.ErrPreAuthKeyFailedToParse) {
        return registrationFailed(401, "malformed auth-key") // do not retry the same string
    }
    return err
}

Prevention

When it happens

Trigger: Calling the registration path with an empty key, a truncated key, a key without the expected prefix (strings.Cut on authKeyPrefix fails), or a non-auth-key credential pasted into --auth-key.

Common situations: Scripts interpolating the key from an unset environment variable (empty string); keys copied with missing characters; passing an API key (hskey-api-...) as an auth-key.

Understand the failure class

Related errors


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