netbirdio/netbird · error

port 0 is not valid for destination address

Error message

port 0 is not valid for destination address

What it means

Returned by validateDestinationPort when the destination port parses as an integer but is exactly 0. Port 0 on a bind address means 'let the OS choose', which is meaningful only for the listening side; a destination must identify a concrete service, so 0 is rejected for the endpoint the forward connects to. This is a sentinel error with no %w wrap — the message is final.

Source

Thrown at client/cmd/ssh.go:693

// Port 0 is only valid for bind addresses (where the OS picks an available port),
// not for destination addresses where we need to connect.
func validateDestinationPort(addr string) error {
	if strings.HasPrefix(addr, "/") || strings.HasPrefix(addr, "./") {
		return nil
	}

	_, portStr, err := net.SplitHostPort(addr)
	if err != nil {
		return fmt.Errorf("parse address %s: %w", addr, err)
	}

	port, err := strconv.Atoi(portStr)
	if err != nil {
		return fmt.Errorf("invalid port %s: %w", portStr, err)
	}

	if port == 0 {
		return fmt.Errorf("port 0 is not valid for destination address")
	}

	if port < 0 || port > 65535 {
		return fmt.Errorf("port %d out of range (1-65535)", port)
	}

	return nil
}

// 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

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Put a real service port on the destination side: 1-65535.
  2. If 0 was meant for the local listener, move it to the bind position (-L 0:host:80) — that side is not port-validated by this function.
  3. In templates/scripts, fail on unset ports instead of defaulting to 0: ${PORT:?port required}.

Example fix

# before
netbird ssh -L 8080:dbhost:0 peer1
# -> invalid remote address: port 0 is not valid for destination address

# after
netbird ssh -L 8080:dbhost:5432 peer1
Defensive patterns

Strategy: validation

Validate before calling

// reject zero ports before they ever reach a spec
if port == 0 {
	return fmt.Errorf("port 0 is only valid on the bind side; destination %q needs 1-65535", target)
}
spec := fmt.Sprintf("%d:%s:%d", bindPort, host, port)

Type guard

func isValidDestinationPort(n int) bool { return n >= 1 && n <= 65535 }

Try / catch

if port == 0 {
	// sentinel, no wrap: handle by re-prompting for the real service port;
	// do not 'fix' it to a guessed default silently
}

Prevention

When it happens

Trigger: `-L 8080:host:0` (remote destination port 0) surfacing as `invalid remote address: port 0 is not valid for destination address`; `-R 9000:localhost:0` (local endpoint port 0) surfacing as `invalid local address: port 0 ...`. Note `-L 0:host:80` is different: that is the local bind port and is not checked by this validator.

Common situations: Reusing an OpenSSH pattern where a zero remote port triggers dynamic/SOCKS-style allocation (not supported here); config templates with PORT=0 meaning 'unassigned'; scripts defaulting unset ports to 0.

Related errors


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