juanfont/headscale · error · ErrPortNumberOutOfRange

port number out of range

Error message

port number out of range

What it means

ErrPortNumberOutOfRange is returned by parsePort (hscontrol/policy/v2/utils.go:146) when a token parses as an integer but falls outside the 0-65535 range ports fit in a uint16. Values like 70000, 99999, or negatives are rejected. (Note single port 0 that reaches the range check passes here but is later rejected by ErrPortMustBePositive in parsePortRange.)

Source

Thrown at hscontrol/policy/v2/utils.go:23

	"fmt"
	"net/netip"
	"slices"
	"strconv"
	"strings"

	"tailscale.com/tailcfg"
)

// Port parsing errors.
var (
	ErrInputMissingColon      = errors.New("input must contain a colon character separating destination and port")
	ErrInputStartsWithColon   = errors.New("input cannot start with a colon character")
	ErrInputEndsWithColon     = errors.New("input cannot end with a colon character")
	ErrInvalidPortRangeFormat = errors.New("invalid port range format")
	ErrPortRangeInverted      = errors.New("invalid port range: first port is greater than last port")
	ErrPortMustBePositive     = errors.New("first port must be >0, or use '*' for wildcard")
	ErrInvalidPortNumber      = errors.New("invalid first integer")
	ErrPortNumberOutOfRange   = errors.New("port number out of range")
	ErrBracketsNotIPv6        = errors.New("square brackets are only valid around IPv6 addresses")
)

// splitDestinationAndPort takes an input string and returns the destination and port as a tuple, or an error if the input is invalid.
// It supports two bracketed IPv6 forms:
//   - "[addr]:port" (RFC 3986, e.g. "[::1]:80")
//   - "[addr]/prefix:port" (e.g. "[fd7a::1]/128:80,443")
//
// Brackets are only accepted around IPv6 addresses, not IPv4, hostnames, or other alias types.
// Bracket stripping reduces both forms to bare "addr:port" or "addr/prefix:port",
// which the normal [strings.LastIndex] of ":" split handles correctly because
// port strings never contain colons.
func splitDestinationAndPort(input string) (string, string, error) {
	// Handle RFC 3986 bracketed IPv6 (e.g. "[::1]:80" or "[fd7a::1]/128:80,443").
	// Strip brackets after validation and fall through to normal parsing.
	if strings.HasPrefix(input, "[") {
		closeBracket := strings.Index(input, "]")
		if closeBracket == -1 {

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Correct the port to a value in 1-65535
  2. For the upper bound remember 65535 is the max, not 65536
  3. Re-check the rule for other typos — an extra digit usually indicates a rushed edit

Example fix

// before
"dst": ["tag:web:44333"]
// after
"dst": ["tag:web:443"]
Defensive patterns

Strategy: validation

Validate before calling

func portInRange(tok string) bool {
    n, err := strconv.Atoi(tok)
    return err == nil && n >= 0 && n <= 65535
}

Try / catch

if errors.Is(err, policyv2.ErrPortNumberOutOfRange) {
    // correct the port to 1-65535; look for accidental extra digits
}

Prevention

When it happens

Trigger: Dst port sections like "host:70000" or "host:65536". Raised when strconv.Atoi succeeds and port < 0 || port > 65535.

Common situations: Typos adding extra digits ("44333" meant "443"); assuming arbitrary integer ranges work; converting configs from systems with different port semantics; template arithmetic producing oversized values.

Related errors


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