juanfont/headscale · error

invalid dst

Error message

invalid dst

What it means

ErrSSHDestinationHostAlias in hscontrol/policy/v2/types.go:56 is a fragment sentinel ("invalid dst") wrapped as invalid dst %q at types.go:2505. It fires when an SSH rule destination entry is not a valid host alias — i.e. not of the form <host-alias>:<user> where host-alias is a tag, hostname, or similar, and <user> is the target OS user.

Source

Thrown at hscontrol/policy/v2/types.go:56

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 (
	ErrACLAutogroupSelfInvalidSource = errors.New("autogroup:self can only be used with users, groups, or supported autogroups")
)

// Grant validation errors.

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Rewrite the dst entry as <alias>:<os-user>, e.g. tag:servers:root or myhost:deploy
  2. Use a defined tag or exact hostname as the alias part, not IPs/CIDRs
  3. Check the quoted dst value in the error message and fix that specific entry

Example fix

// before
{"action": "accept", "users": ["user1"], "dst": ["tag:servers"]}
// after
{"action": "accept", "users": ["user1"], "dst": ["tag:servers:root"]}
Defensive patterns

Strategy: validation

Validate before calling

// ssh dst must be <alias>:<os-user>
parts := strings.Split(dst, ":")
if len(parts) < 2 || parts[len(parts)-1] == "" { return fmt.Errorf("dst %q missing :user", dst) }

Type guard

func isSSHDstForm(dst string) bool {
    i := strings.LastIndex(dst, ":")
    return i > 0 && i < len(dst)-1
}

Try / catch

if errors.Is(err, hpolicy.ErrSSHDestinationHostAlias) { /* error quotes the bad dst; append :<user> */ }

Prevention

When it happens

Trigger: An ssh rule dst entry missing the :user suffix (e.g. "tag:servers" instead of "tag:servers:root"), containing a bare "*" (which ErrSSHWildcardDestination also covers), or otherwise failing host-alias parsing at types.go:2505. The raw dst string is included in the error.

Common situations: Writing only the host without the OS user — the most common first mistake with SSH ACLs; pasting IP addresses or CIDRs (valid in ACL dst but not ssh dst); forgetting the tag: prefix.

Related errors


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