juanfont/headscale · error
user destination requires source to contain only that same u
Error message
user destination requires source to contain only that same user
What it means
SSH validation error (hscontrol/policy/v2/types.go:2178-2181): an SSH rule's dst is a specific user, but its src contains anything other than exactly that same user. Tailscale requires user-owned SSH destinations to be reachable in SSH rules only from the owning user (plus groups containing them); any other alias in src fails validation.
Source
Thrown at hscontrol/policy/v2/types.go:43
)
// Global JSON options for consistent parsing across all struct unmarshaling.
var policyJSONOpts = []json.Options{
json.DefaultOptionsV2(),
json.MatchCaseInsensitiveNames(true),
json.RejectUnknownMembers(true),
}
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:View on GitHub (pinned to 565fd254d0)
Solutions
- Restrict src to exactly the dst user: {"src": ["user:alice"], "dst": ["user:alice"]}
- Or move dst to autogroup:self so each user reaches their own devices under one rule
- For admin access to user devices, have the users' nodes tagged or rely on Tailscale's check-action model instead
Example fix
// before
{"src": ["group:devs"], "dst": ["user:alice"], "users": ["alice"], "action": "accept"}
// after
{"src": ["group:devs"], "dst": ["autogroup:self"], "users": ["autogroup:nonroot"], "action": "check", "checkPeriod": "8h"} Defensive patterns
Strategy: validation
Validate before calling
func sshUserDstRuleValid(src []string, dst string) bool { userDsts := filterNonTags(dst); if len(userDsts) == 0 { return true }; for _, d := range userDsts { if len(src) != 1 || src[0] != d { return false } }; return true } Prevention
- Prefer dst autogroup:self over per-user rules
- Never list extra sources beside a user dst
- Use check actions for admin-style access instead
When it happens
Trigger: {"src": ["user:alice", "user:bob"], "dst": ["user:alice"], ...} or {"src": ["group:devs"], "dst": ["user:alice"]} where the group contains more than alice — the error names the offending source value.
Common situations: Trying to grant admins SSH into all user devices (not expressible per-user; use a different model); listing multiple users in src for convenience; misunderstanding that dst-user implies src-user equality.
Related errors
- tags in SSH source cannot access user-owned devices
- autogroup:self destination requires source to contain only u
- tags in SSH source cannot access autogroup:member (user-owne
- wildcard (*) is not supported as SSH destination
- is above the max (168h)
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/20e984d5b8afdbbd.
Report an issue: GitHub.