juanfont/headscale · error

invalid protocol number

Error message

invalid protocol number

What it means

Returned by Protocol.validate (hscontrol/policy/v2/types.go:1775) when an ACL rule's "proto" value is neither a known protocol name (icmp, igmp, ipv4, ipinip, tcp, egp, igp, udp, gre, esp, ah, sctp, ipv6-icmp, fc) nor parseable as an integer protocol number. The error message reminds that the value must be a known name or a number 0-255.

Source

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

	ErrInvalidHostname             = errors.New("invalid hostname")
	ErrHostResolve                 = errors.New("error resolving host")
	ErrInvalidPrefix               = errors.New("invalid prefix")
	ErrInvalidAutogroup            = errors.New("invalid autogroup")
	ErrUnknownAutogroup            = errors.New("unknown autogroup")
	ErrHostportMissingColon        = errors.New("hostport must contain a colon")
	ErrTypeNotSupported            = errors.New("type not supported")
	ErrInvalidAlias                = errors.New("invalid alias format")
	ErrInvalidAutoApprover         = errors.New("invalid auto approver format")
	ErrInvalidOwner                = errors.New("invalid owner format")
	ErrGroupNotDefined             = errors.New("group not defined in policy")
	ErrInvalidGroupMember          = errors.New("invalid group member type")
	ErrGroupValueNotArray          = errors.New("group value must be an array of users")
	ErrInvalidHostIP               = errors.New("hostname contains invalid IP address")
	ErrTagNotDefined               = errors.New("tag not found")
	ErrAutoApproverNotAlias        = errors.New("auto approver is not an alias")
	ErrInvalidACLAction            = errors.New("invalid ACL action")
	ErrInvalidSSHAction            = errors.New("invalid SSH action")
	ErrInvalidProtocolNumber       = errors.New("invalid protocol number")
	ErrProtocolLeadingZero         = errors.New("leading 0 not permitted in protocol number")
	ErrProtocolOutOfRange          = errors.New("protocol number out of range (0-255)")
	ErrAutogroupNotSupported       = errors.New("autogroup not supported in headscale")
	ErrAutogroupInternetSrc        = errors.New("autogroup:internet can only be used in ACL destinations")
	ErrAutogroupSelfSrc            = errors.New("\"autogroup:self\" not valid on the src side of a rule")
	ErrAutogroupNotSupportedACLSrc = errors.New("autogroup not supported for ACL sources")
	ErrAutogroupNotSupportedACLDst = errors.New("autogroup not supported for ACL destinations")
	ErrAutogroupDangerAllDst       = errors.New("cannot use autogroup:danger-all as a dst")
	ErrAutogroupNotSupportedSSHSrc = errors.New("autogroup not supported for SSH sources")
	ErrAutogroupNotSupportedSSHDst = errors.New("autogroup not supported for SSH destinations")
	ErrHostNotDefined              = errors.New("host not defined in policy")
	ErrSSHSourceAliasNotSupported  = errors.New("alias not supported for SSH source")
	ErrSSHDestAliasNotSupported    = errors.New("alias not supported for SSH destination")
	ErrUnknownField                = errors.New("unknown field")
	ErrProtocolNoSpecificPorts     = errors.New("protocol does not support specific ports")
	ErrTestEmptyAssertions         = errors.New("test entry must have at least one of \"accept\" or \"deny\"")
	ErrTestProtocolNotAllowed      = errors.New("test protocol must be tcp, udp, sctp, or empty")
	ErrTestDestinationMultiPort    = errors.New("test destination port must be a single port")

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Use an IANA protocol name from the supported set (tcp, udp, icmp, sctp, gre, esp, ah, ...)
  2. Or use the numeric IP protocol number (0-255), e.g. 47 for GRE
  3. For multiple protocols, write one ACL rule per protocol, or omit "proto" entirely to match all

Example fix

// before
{"action": "accept", "proto": "http", "src": ["group:dev"], "dst": ["tag:web:80"]}

// after
{"action": "accept", "proto": "tcp", "src": ["group:dev"], "dst": ["tag:web:80"]}
Defensive patterns

Strategy: validation

Validate before calling

var knownProto = map[string]bool{"icmp": true, "igmp": true, "ipv4": true, "ipinip": true, "tcp": true, "egp": true, "igp": true, "udp": true, "gre": true, "esp": true, "ah": true, "sctp": true, "ipv6-icmp": true, "fc": true}
func validProto(s string) bool {
	if knownProto[s] || s == "" {
		return true
	}
	n, err := strconv.Atoi(s)
	return err == nil && n >= 0 && n <= 255
}

Type guard

func isInvalidProtocolNumber(err error) bool {
	return errors.Is(err, policy.ErrInvalidProtocolNumber)
}

Try / catch

if err := json.Unmarshal(b, &acl); err != nil {
	if errors.Is(err, policy.ErrInvalidProtocolNumber) {
		return fmt.Errorf("proto must be an IANA name or 0-255: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Policy with "proto": "tcp/udp" (protocols are one per rule), "proto": "http" (http is not an IP protocol), "proto": "TLS", or any non-numeric non-name string. Fires during ACL unmarshalling at policy load.

Common situations: Confusing application-layer protocols (http, https, ssh-as-proto) with IP protocols; trying to express multiple protocols in one rule; copy-paste from other firewall syntax.

Related errors


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