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, ErrPreAuthKeyNotFoundView on GitHub (pinned to 565fd254d0)
Solutions
- Re-copy the full authkey from `headscale preauthkeys create` output — it must be one unbroken string
- Check the variable holding the key is set and unmodified in your provisioning script (print its length, not the value)
- 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
- Assert key variables are non-empty (:- or :? shell expansions) before use
- Never paste other credential types (hskey-api-, hskey-oauthtok-) into --auth-key
- Keep keys whole in secret storage; avoid string surgery on credentials
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to parse ApiKey
- failed to parse oauth client secret
- auth-key expired
- auth-key has already been used
- user mismatch
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/1b6bc2b9141975fd.
Report an issue: GitHub.