juanfont/headscale · error
is not valid
Error message
is not valid
What it means
ErrSSHUserInvalid in hscontrol/policy/v2/types.go:52 is a fragment-style sentinel ("is not valid") wrapped as user %q is not valid at types.go:2435. It fires when an SSH rule users entry is not one of the accepted forms: a plain username, autogroup:nonroot, autogroup:self (subject to SSH rules), or the localpart:*@<domain> pattern.
Source
Thrown at hscontrol/policy/v2/types.go:52
const Wildcard = Asterix(0)
var ErrAutogroupSelfRequiresPerNodeResolution = errors.New("autogroup:self requires per-node resolution and cannot be resolved in this context")
var ErrUndefinedTagReference = errors.New("references undefined tag")
// SSH validation errors.
var (
ErrSSHTagSourceToUserDest = errors.New("tags in SSH source cannot access user-owned devices")
ErrSSHUserDestRequiresSameUser = errors.New("user destination requires source to contain only that same user")
ErrSSHAutogroupSelfRequiresUserSource = errors.New("autogroup:self destination requires source to contain only users or groups, not tags or autogroup:tagged")
ErrSSHTagSourceToAutogroupMember = errors.New("tags in SSH source cannot access autogroup:member (user-owned devices)")
ErrSSHWildcardDestination = errors.New("wildcard (*) is not supported as SSH destination")
ErrSSHCheckPeriodAboveMax = errors.New("is above the max (168h)")
ErrSSHCheckPeriodNegative = errors.New("must be a positive duration")
ErrSSHCheckPeriodOnNonCheck = errors.New("checkPeriod is only valid with action \"check\"")
ErrInvalidLocalpart = errors.New("invalid localpart format, must be localpart:*@<domain>")
ErrSSHUsersMustBeSpecified = errors.New("users must be specified")
ErrSSHUserInvalid = errors.New("is not valid")
ErrSSHAcceptEnvEmpty = errors.New("acceptEnv values cannot be empty")
ErrSSHActionMustBeSpecified = errors.New("action must be specified")
ErrSSHActionInvalid = errors.New("is not a valid action")
ErrSSHDestinationHostAlias = errors.New("invalid dst")
ErrTagNameMustStartWithLetter = errors.New("tag names must start with a letter, after 'tag:'")
ErrGroupMembersCannotBeRecursive = errors.New("group members cannot be recursive")
)
// SSH check period constants per Tailscale docs:
// https://tailscale.com/docs/features/tailscale-ssh#checkperiod
// SaaS imposes no minimum (0s is accepted) so headscale matches.
const (
SSHCheckPeriodDefault = 12 * time.Hour
SSHCheckPeriodMax = 7 * 24 * time.Hour
)
// ACL validation errors.
var (View on GitHub (pinned to 565fd254d0)
Solutions
- Check the offending user value quoted in the error and replace it with a valid form: plain username, autogroup:nonroot, or localpart:*@<domain>
- For group-style access, enumerate the group's users in the users array or restructure the rule per docs/ ACL reference
- Re-validate and re-apply the policy
Example fix
// before
{"users": ["group:admins"], "dst": ["tag:srv:admin"], "action": "accept"}
// after
{"users": ["alice", "bob"], "dst": ["tag:srv:admin"], "action": "accept"} Defensive patterns
Strategy: validation
Validate before calling
func validSSHUser(u string) bool {
if u == "autogroup:nonroot" { return true }
if validLocalpartUser(u) { return true }
return !strings.ContainsAny(u, ":@*") // plain username
} Type guard
func isSSHUserForm(u string) bool {
return u == "autogroup:nonroot" || strings.HasPrefix(u, "localpart:*") || !strings.Contains(u, ":")
} Try / catch
if errors.Is(err, hpolicy.ErrSSHUserInvalid) { /* error quotes the offending user; fix that entry */ } Prevention
- Never put groups or * in ssh users
- Keep a cheat sheet: ssh users accept username | autogroup:nonroot | localpart:*@domain
When it happens
Trigger: An ssh rule users entry like "group:admins" (groups are not allowed in ssh users in this build), "*", an email with an unsupported form, or a user string failing validation at types.go:2435. The error message includes the offending user value.
Common situations: Porting ACL src entries (which allow groups and *) into ssh users where they are not accepted; assuming Tailscale SaaS grant syntax equals headscale ssh syntax; referencing a group defined in the policy inside an SSH rule.
Related errors
- must be a positive duration
- checkPeriod is only valid with action "check"
- invalid localpart format, must be localpart:*@<domain>
- users must be specified
- action must be specified
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/c9f4475f0a20d6ce.
Report an issue: GitHub.