juanfont/headscale · warning · util.ErrUsernameMustStartLetter

username must start with a letter

Error message

username must start with a letter

What it means

ErrUsernameMustStartLetter (hscontrol/util/dns.go:31) is returned by ValidateUsername when the first character of the username is not an ASCII letter. This mirrors DNS-label and classic username rules (RFC 1123/952 heritage referenced by the constants in dns.go): names must start with a letter, then may contain letters, digits, hyphens, dots, underscores, and one '@'.

Source

Thrown at hscontrol/util/dns.go:31

	"tailscale.com/util/dnsname"
)

const (
	ByteSize          = 8
	ipv4AddressLength = 32
	ipv6AddressLength = 128

	// LabelHostnameLength is the maximum length for a DNS label,
	// value related to RFC 1123 and 952.
	LabelHostnameLength = 63
)

// DNS validation errors. Hostname-side validation lives on
// `tailscale.com/util/dnsname` and [state.NodeStore] collision handling; only
// the username-side errors stay in this package.
var (
	ErrUsernameTooShort        = errors.New("username must be at least 2 characters long")
	ErrUsernameMustStartLetter = errors.New("username must start with a letter")
	ErrUsernameTooManyAt       = errors.New("username cannot contain more than one '@'")
	ErrUsernameInvalidChar     = errors.New("username contains invalid character")
)

// ValidateUsername checks if a username is valid.
// It must be at least 2 characters long, start with a letter, and contain
// only letters, numbers, hyphens, dots, and underscores.
// It cannot contain more than one '@'.
// It cannot contain invalid characters.
func ValidateUsername(username string) error {
	// Ensure the username meets the minimum length requirement
	if len(username) < 2 {
		return ErrUsernameTooShort
	}

	// Ensure the username starts with a letter
	if !unicode.IsLetter(rune(username[0])) {
		return ErrUsernameMustStartLetter

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Prefix or rename so the name starts with a letter (e.g. u123456 instead of 123456)
  2. For OIDC, transform the incoming claim (prefix with a letter) before user creation
  3. Run util.ValidateUsername in provisioning scripts to fail before the API call

Example fix

# before
headscale users create 1alice

# after
headscale users create alice1
Defensive patterns

Strategy: validation

Validate before calling

func startsWithLetter(s string) bool {
	return len(s) > 0 && ((s[0] >= 'a' && s[0] <= 'z') || (s[0] >= 'A' && s[0] <= 'Z'))
}
if !startsWithLetter(username) {
	return fmt.Errorf("username %q must start with a letter", username)
}

Prevention

When it happens

Trigger: Calling util.ValidateUsername with names like "1alice", "_bob", "-carol", or ".dave" — any leading non-letter. Reached from CLI user creation, API user creation, and OIDC-derived usernames.

Common situations: Provisioning machine-generated usernames that begin with a digit (e.g. student ID "123456"); email local-parts starting with a dot or digit; import scripts from systems with laxer naming rules.

Related errors


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