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

  1. 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.
  2. Put a concrete port on the destination; 0 is only valid for the local bind side, never the destination.
  3. Bracket IPv6 hosts: -L [::1]:8080:host:80.
  4. 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

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


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/387d959739096072. Report an issue: GitHub.