juanfont/headscale · error · ErrInputMissingColon
input must contain a colon character separating destination
Error message
input must contain a colon character separating destination and port
What it means
ErrInputMissingColon is returned by splitDestinationAndPort (hscontrol/policy/v2/utils.go:67) when a destination-and-port string contains no ':' at all, so destination and port cannot be separated. Every ACL/grant destination must use the "destination:port" form (or the bracketed IPv6 forms "[addr]:port" / "[addr]/prefix:port"). It propagates out of destination parsing during policy load.
Source
Thrown at hscontrol/policy/v2/utils.go:16
package v2
import (
"errors"
"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 becauseView on GitHub (pinned to 565fd254d0)
Solutions
- Append a port to the destination: ":22", ":80,443", or ":*"
- For IPv6, use bracketed form "[fd7a::1]:22" (brackets are required, see ErrBracketsNotIPv6)
- Check the erroring rule's dst array for any entry lacking a colon
Example fix
// before "dst": ["tag:server"] // after "dst": ["tag:server:*"]
Defensive patterns
Strategy: validation
Validate before calling
// Check a dst string is splittable before adding to a policy
func dstHasPort(dst string) bool {
i := strings.LastIndex(dst, ":")
return i > 0 && i < len(dst)-1
} Try / catch
if errors.Is(err, policyv2.ErrInputMissingColon) {
// append a port (":*" for any) to the destination
} Prevention
- Always write destinations as destination:port
- Use ":*" when any port is intended
- Lint policies for dst entries without a colon
When it happens
Trigger: A dst like "tag:server" — note the colon in "tag:" does not count only if... actually "tag:server:22" is fine; the error fires for inputs with zero colons such as "10.0.0.1", "example-host", "autogroup:member" (no port). Raised when strings.LastIndex(input, ":") == -1.
Common situations: Forgetting the port suffix when writing dst entries; using a bare hostname where "host:*" was intended; YAML anchors or templating that strip the trailing ":*" portion.
Related errors
- input cannot start with a colon character
- input cannot end with a colon character
- first port must be >0, or use '*' for wildcard
- invalid first integer
- port number out of range
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/9d825708a71f06d0.
Report an issue: GitHub.