netbirdio/netbird · error

remote port forward %s: %w

Error message

remote port forward %s: %w

What it means

Returned by startPortForwarding when parseAndStartRemoteForward rejects one -R/--remote-forward spec. Structurally identical to the local case: the wrap names the failing spec and the cause is either parsePortForwardSpec (colon structure) or validateDestinationPort applied to the local address — in the -R flow the 'destination' the validator checks is the localAddr the remote side will dial back to.

Source

Thrown at client/cmd/ssh.go:623

			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
	}

	if err := validateDestinationPort(remoteAddr); err != nil {
		return fmt.Errorf("invalid remote address: %w", err)
	}

	log.Debugf("Local port forwarding: %s -> %s", localAddr, remoteAddr)

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Use the accepted forms with the local target fully specified: -R [remote_host:]remote_port:local_host:local_port, local port 1-65535 (e.g., -R 9000:localhost:8080).
  2. Give the local endpoint a concrete non-zero port — 0 is rejected because the remote side must connect to something definite.
  3. Bracket IPv6 bind hosts and keep the spec to 2-4 colon parts.
  4. Check the wrapped tail (`invalid local address: ...`) to see exactly which sub-check failed, then fix that segment.

Example fix

# before
netbird ssh -R 9000:localhost peer1
# -> start port forwarding: remote port forward 9000:localhost: invalid port forward specification ...

# after
netbird ssh -R 9000:localhost:8080 peer1
Defensive patterns

Strategy: validation

Validate before calling

// -R grammar: [remote_host:]remote_port:local_host:local_port
func buildRemoteForward(remotePort int, localHost string, localPort int) (string, error) {
	if remotePort < 1 || remotePort > 65535 || localPort < 1 || localPort > 65535 {
		return "", fmt.Errorf("ports must be 1-65535, got remote=%d local=%d", remotePort, localPort)
	}
	if localHost == "" {
		localHost = "127.0.0.1"
	}
	return fmt.Sprintf("%d:%s:%d", remotePort, localHost, localPort), nil
}

Try / catch

if err := parseAndStartRemoteForward(ctx, c, forward, cmd); err != nil {
	// parse or local-endpoint port validation failed on this spec;
	// report and continue/abort — no listener was created for it
	log.Printf("skip remote forward %q: %v", forward, err)
}

Prevention

When it happens

Trigger: `netbird ssh -R 9000:localhost peer` (two parts, second not a unix path), `-R 9000:localhost:0` (local port 0), `-R 9000:localhost:70000` (out of range), `-R bindhost:9000:localhost:80:extra` (5 parts), or any spec whose validated local endpoint fails SplitHostPort/Atoi/range checks.

Common situations: Mirroring an OpenSSH -R habit of omitting the local port; expecting the remote side to pick the port; specs built from variables where one segment is empty; forgetting that in -R the last host:port pair is the local target your machine serves.

Related errors


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