netbirdio/netbird · error

start port forwarding: %w

Error message

start port forwarding: %w

What it means

Returned by runSSH when startPortForwarding fails before the interactive session or remote command starts. startPortForwarding only iterates the -L/--local-forward and -R/--remote-forward flag values and delegates to parseAndStartLocalForward/parseAndStartRemoteForward, so this error always wraps either a parsePortForwardSpec failure (malformed spec) or a validateDestinationPort failure (bad/zero/out-of-range port) — the connection itself is already up at this point.

Source

Thrown at client/cmd/ssh.go:556

		cmd.Printf("\nTroubleshooting steps:\n")
		cmd.Printf("  1. Check peer connectivity: netbird status -d\n")
		cmd.Printf("  2. Verify SSH server is enabled on the peer\n")
		cmd.Printf("  3. Ensure correct hostname/IP is used\n")
		return fmt.Errorf("dial %s: %w", target, err)
	}

	sshCtx, cancel := context.WithCancel(ctx)
	defer cancel()

	go func() {
		<-sshCtx.Done()
		if err := c.Close(); err != nil {
			cmd.Printf("Error closing SSH connection: %v\n", err)
		}
	}()

	if err := startPortForwarding(sshCtx, c, cmd); err != nil {
		return fmt.Errorf("start port forwarding: %w", err)
	}

	if command != "" {
		return executeSSHCommand(sshCtx, c, command)
	}
	return openSSHTerminal(sshCtx, c)
}

// executeSSHCommand executes a command over SSH.
func executeSSHCommand(ctx context.Context, c *sshclient.Client, command string) error {
	var err error
	if requestPTY {
		err = c.ExecuteCommandWithPTY(ctx, command)
	} else {
		err = c.ExecuteCommandWithIO(ctx, command)
	}

	if err != nil {

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Read the wrapped message: `start port forwarding: local port forward <spec>: ...` or `remote port forward <spec>: ...` tells you which flag and spec failed.
  2. Fix the spec to one of the accepted forms: [local_host:]local_port:remote_target with 2-4 colon parts (2-part only valid as port:/unix/socket), e.g. -L 8080:localhost:80.
  3. Give the destination a concrete port 1-65535; 0 is only valid on the bind side, not the destination.
  4. Bracket IPv6 local hosts: -L [::1]:8080:host:80.
  5. Re-run; parsing happens after dial, so no reconnect cost concerns — the failure is purely local input validation.

Example fix

# before
netbird ssh -L 8080:remotehost peer1
# -> start port forwarding: local port forward 8080:remotehost: invalid port forward specification ...

# after
netbird ssh -L 8080:remotehost:80 peer1
Defensive patterns

Strategy: validation

Validate before calling

// validate every -L/-R spec before invoking netbird ssh
func validateForwardSpecs(specs []string) error {
	for _, s := range specs {
		parts := strings.Split(s, ":")
		if len(parts) < 2 || len(parts) > 4 {
			return fmt.Errorf("spec %q must have 2-4 colon parts", s)
		}
	}
	return nil
}
// plus run the same validateDestinationPort logic from error 371 on the destination

Try / catch

if err := startPortForwarding(sshCtx, c, cmd); err != nil {
	// err always wraps a parse/port validation failure of one spec;
	// the session is still up: log, skip the bad spec or abort per policy
	log.Printf("forwarding setup failed: %v", err)
}

Prevention

When it happens

Trigger: `netbird ssh -L <bad-spec> host` or `-R <bad-spec>` where the spec has fewer than 2 or more than 4 colon-separated parts, uses a two-part host form without a unix socket path, or where the destination address fails port validation (unparseable, non-numeric, 0, or >65535).

Common situations: Copy-pasting OpenSSH forward syntax with extra segments; forgetting the remote port in `8080:host`; using port 0 expecting the OS to pick a port on the destination side (only valid for bind side); IPv6 addresses passed unbracketed so colon counting breaks.

Related errors


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