juanfont/headscale · error
port range %q: %w
Error message
port range %q: %w
What it means
ProtocolPort.UnmarshalJSON hit the bare-port branch (string with no ':') and parsePortRange rejected it. The value was meant to be just a port or port range ('80', '8080-8090', or '*') but the fragment is malformed.
Source
Thrown at hscontrol/policy/v2/types.go:946
err := json.Unmarshal(b, &v)
if err != nil {
return err
}
switch vs := v.(type) {
case string:
if vs == "*" {
ve.Protocol = ProtocolNameWildcard
ve.Ports = []tailcfg.PortRange{tailcfg.PortRangeAny}
return nil
}
// Only contains a port, no protocol
if !strings.Contains(vs, ":") {
ports, err := parsePortRange(vs)
if err != nil {
return fmt.Errorf("port range %q: %w", vs, err)
}
ve.Protocol = ProtocolNameWildcard
ve.Ports = ports
return nil
}
parts := strings.Split(vs, ":")
if len(parts) != 2 {
return fmt.Errorf("%w, got: %v(%d)", ErrProtocolPortInvalidFormat, parts, len(parts))
}
protocol := Protocol(parts[0])
err := protocol.validate()
if err != nil {
return errView on GitHub (pinned to 565fd254d0)
Solutions
- Write protocol-qualified forms: 'tcp:22', 'udp:53', 'tcp:8080-8090'.
- If you meant a bare wildcard, use '*' exactly.
- Replace service names with their numeric ports.
Example fix
// before "protocols": ["ssh"] // after "protocols": ["tcp:22"]
Defensive patterns
Strategy: validation
Validate before calling
var barePortRe = regexp.MustCompile(`^(\*|[0-9]+(-[0-9]+)?)$`)
if !strings.Contains(s, ":") && !barePortRe.MatchString(s) {
return fmt.Errorf("%q is neither '*'/port-range nor proto:port; did you mean 'tcp:%s'?", s, s)
} Try / catch
if err := pp.UnmarshalJSON([]byte(s)); err != nil {
if strings.Contains(err.Error(), "port range") {
// likely a protocol name without ':port'; rewrite as proto:port
}
return err
} Prevention
- Default to the qualified form 'tcp:22'/'udp:53'.
- Use numbers, not service names.
- '*' is the only valid bare word.
When it happens
Trigger: Grant protocols entries like "80-" , "http" (a word, not a port, and no colon so it is treated as a port), or "99999". strings.Contains(vs, ":") is false and parsePortRange(vs) errors.
Common situations: Forgetting the protocol prefix: writing "tcp" or "80" where "tcp:80" was intended; service names instead of numbers ('ssh' instead of '22').
Related errors
- %w, got: %v(%d)
- dst=%q: port range %q: %w
- protocol does not support specific ports
- invalid destination %q: %w
- %w, got: %q
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/51771d8b07884a7c.
Report an issue: GitHub.