netbirdio/netbird · error
local port forward %s: %w
Error message
local port forward %s: %w
What it means
Returned by startPortForwarding when parseAndStartLocalForward rejects one -L/--local-forward spec before any listener starts. The wrap carries the exact spec, and the underlying error is either a parsePortForwardSpec failure (malformed colon structure) or validateDestinationPort rejecting the remote target (unparseable address, non-numeric port, port 0, or out-of-range port). Parsing stops at the first bad spec — later forwards are not attempted.
Source
Thrown at client/cmd/ssh.go:617
return nil
}
var exitMissingErr *ssh.ExitMissingError
if errors.As(err, &exitMissingErr) {
log.Debugf("Remote terminal exited without exit status: %v", err)
return nil
}
return fmt.Errorf("open terminal: %w", err)
}
return nil
}
// startPortForwarding starts local and remote port forwarding based on command line flags
func startPortForwarding(ctx context.Context, c *sshclient.Client, cmd *cobra.Command) error {
for _, forward := range localForwards {
if err := parseAndStartLocalForward(ctx, c, forward, cmd); err != nil {
return fmt.Errorf("local port forward %s: %w", forward, err)
}
}
for _, forward := range remoteForwards {
if err := parseAndStartRemoteForward(ctx, c, forward, cmd); err != nil {
return fmt.Errorf("remote port forward %s: %w", forward, err)
}
}
return nil
}
// parseAndStartLocalForward parses and starts a local port forward (-L)
func parseAndStartLocalForward(ctx context.Context, c *sshclient.Client, forward string, cmd *cobra.Command) error {
localAddr, remoteAddr, err := parsePortForwardSpec(forward)
if err != nil {
return err
}View on GitHub (pinned to 93e97f4bf1)
Solutions
- Match one of the accepted forms: port:/path/to/socket, host:port:/path/to/socket, port:host:hostport, or host:port:host:hostport — destination must end in a unix path or a host:port with port 1-65535.
- Put a concrete port on the destination; 0 is only valid for the local bind side, never the destination.
- Bracket IPv6 hosts: -L [::1]:8080:host:80.
- Quote the whole spec in the shell so colons survive intact, and check the wrapped message tail for which sub-validation (parse vs port) failed.
Example fix
# before netbird ssh -L 8080:peer1 peer1 # -> start port forwarding: local port forward 8080:peer1: invalid port forward specification: ... # after netbird ssh -L 8080:peer1:80 peer1
Defensive patterns
Strategy: validation
Validate before calling
// mirror the CLI grammar for -L before building the command line
func buildLocalForward(localPort int, remoteHost string, remotePort int) (string, error) {
if localPort < 0 || remotePort < 1 || remotePort > 65535 {
return "", fmt.Errorf("ports out of range: local=%d remote=%d", localPort, remotePort)
}
if remoteHost == "" {
return "", errors.New("remote host required")
}
return fmt.Sprintf("%d:%s:%d", localPort, remoteHost, remotePort), nil
} Type guard
// matches port:host:port, host:port:host:port, or port:/unix/socket var forwardSpecRe = regexp.MustCompile(`^([^:]+):([^:]+)(?::([^:]+))?(?::([^:]+))?$`)
Try / catch
if err := parseAndStartLocalForward(ctx, c, forward, cmd); err != nil {
// wrapped cause is parse or port validation on this exact spec string;
// surface spec + cause, keep other forwards running or abort per policy
log.Printf("skip local forward %q: %v", forward, err)
} Prevention
- Always end a TCP destination with :numeric-port; sockets must start with / or ./.
- Bracket IPv6 local hosts and remember the remote side of the grammar is name/IPv4 only.
- Generate specs from typed values (ints for ports), never by string concatenation of raw config.
- Lint forward flags in wrapper scripts with a 2-4-part colon count check before exec.
When it happens
Trigger: `netbird ssh -L 8080:localhost peer` (two parts, second not a unix path), `-L 8080:host:0` (destination port 0), `-L 8080:host:99999` (out of range), `-L 8080:host:abc` (non-numeric), unbracketed IPv6 like `-L ::1:8080:host:80` (5 colon parts), or any spec with fewer than 2 / more than 4 colon-separated parts.
Common situations: Assuming OpenSSH's laxer parsing where trailing parts are ignored; forgetting the destination port; expecting port 0 to mean 'pick one' on the remote side; pasting forward lists where one entry lost a segment in shell quoting.
Related errors
- start port forwarding: %w
- remote port forward %s: %w
- invalid remote address: %w
- invalid local address: %w
- invalid port forward specification: %s (expected format: [lo
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/387d959739096072.
Report an issue: GitHub.