netbirdio/netbird · error
invalid port forward specification: %s (expected format: [lo
Error message
invalid port forward specification: %s (expected format: [local_host:]local_port:remote_target)
What it means
Returned by parsePortForwardSpec when splitting the spec on ':' yields fewer than two parts — i.e., the spec contains no colon at all. The message embeds the expected grammar `[local_host:]local_port:remote_target`. This fires before the 2/3/4-part handlers run, so a single-token spec like `8080` never reaches socket/port validation.
Source
Thrown at client/cmd/ssh.go:719
}
// parsePortForwardSpec parses port forward specifications like "8080:localhost:80" or "[::1]:8080:localhost:80".
// Also supports Unix sockets like "8080:/tmp/socket" or "127.0.0.1:8080:/tmp/socket".
func parsePortForwardSpec(spec string) (string, string, error) {
// Support formats:
// port:host:hostport -> localhost:port -> host:hostport
// host:port:host:hostport -> host:port -> host:hostport
// [host]:port:host:hostport -> [host]:port -> host:hostport
// port:unix_socket_path -> localhost:port -> unix_socket_path
// host:port:unix_socket_path -> host:port -> unix_socket_path
if strings.HasPrefix(spec, "[") && strings.Contains(spec, "]:") {
return parseIPv6ForwardSpec(spec)
}
parts := strings.Split(spec, ":")
if len(parts) < 2 {
return "", "", fmt.Errorf("invalid port forward specification: %s (expected format: [local_host:]local_port:remote_target)", spec)
}
switch len(parts) {
case 2:
return parseTwoPartForwardSpec(parts, spec)
case 3:
return parseThreePartForwardSpec(parts)
case 4:
return parseFourPartForwardSpec(parts)
default:
return "", "", fmt.Errorf("invalid port forward specification: %s", spec)
}
}
// parseTwoPartForwardSpec handles "port:unix_socket" format.
func parseTwoPartForwardSpec(parts []string, spec string) (string, string, error) {
if isUnixSocket(parts[1]) {
localAddr := "localhost:" + parts[0]View on GitHub (pinned to 93e97f4bf1)
Solutions
- Write the spec with at least two colon-separated parts: for -L the minimum full form is local_port:remote_host:remote_port (or port:/unix/path for sockets).
- Quote the argument: -L "8080:localhost:80" so no shell or config layer can split it.
- If the value comes from config, print it once at render time to confirm colons survived.
Example fix
# before netbird ssh -L 8080 peer1 # -> invalid port forward specification: 8080 (expected format: [local_host:]local_port:remote_target) # after netbird ssh -L 8080:localhost:80 peer1
Defensive patterns
Strategy: validation
Validate before calling
if !strings.Contains(spec, ":") {
return fmt.Errorf("forward spec %q must contain at least one ':' (port:target...)", spec)
} Type guard
func hasForwardColon(s string) bool { return strings.Count(s, ":") >= 1 } Try / catch
if len(parts) < 2 {
// spec had no colon at all: treat as a construction bug upstream,
// log the raw spec + its source (env var/config key) for tracing
} Prevention
- Quote forward specs in every layer: -L "8080:host:80".
- When rendering from templates, assert the rendered string still contains colons before exec.
- Store forwards as structured data (host+ports) and serialize only at invocation; never hand-edit strings.
- Add a one-line sanity check in wrappers: [[ $spec == *:* ]] || exit with a clear message.
When it happens
Trigger: `netbird ssh -L 8080 peer` (a bare port with no colon), `-R 9000` , or a spec whose colon was eaten by shell parsing/quoting so only one field remains. Applies identically to -L and -R since both call parsePortForwardSpec first.
Common situations: Assuming the flag takes just a port and the target is implied; YAML/JSON or env-file values losing the colon; HCL/terraform heredocs interpreting `8080:host:80` and emitting only the first token; muscle memory from tools with different flag shapes.
Related errors
- start port forwarding: %w
- local port forward %s: %w
- remote port forward %s: %w
- invalid remote address: %w
- invalid local address: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/73efca4a29450977.
Report an issue: GitHub.