netbirdio/netbird · error
invalid port %s: %w
Error message
invalid port %s: %w
What it means
Produced inside validateDestinationPort when the port segment of a destination address splits cleanly but is not a base-10 integer — strconv.Atoi fails on it. This rejects service names (http, ssh), empty strings, trailing whitespace, and any non-numeric characters; a leading + or - is accepted by Atoi, so `-1` fails later in the range check instead.
Source
Thrown at client/cmd/ssh.go:689
return nil
}
// validateDestinationPort checks that the destination address has a valid port.
// 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:hostportView on GitHub (pinned to 93e97f4bf1)
Solutions
- Replace the service name with its numeric port: https -> 443, http -> 80, ssh -> 22.
- Strip protocol suffixes and whitespace from scripted inputs (`${PORT%%/*}` and trims).
- Echo the final spec when it is variable-built to catch empty port segments before invoking netbird ssh.
Example fix
# before netbird ssh -L 8443:intranet:https peer1 # -> invalid remote address: invalid port https: strconv.Atoi: parsing "https": invalid syntax # after netbird ssh -L 8443:intranet:443 peer1
Defensive patterns
Strategy: validation
Validate before calling
// accept only numeric ports, resolve names once at config load
svcPorts := map[string]int{"http": 80, "https": 443, "ssh": 22, "postgres": 5432}
func normalizePort(p string) (int, error) {
p = strings.TrimSpace(strings.TrimSuffix(strings.TrimSuffix(p, "/tcp"), "/udp"))
if n, ok := svcPorts[p]; ok {
return n, nil
}
n, err := strconv.Atoi(p)
if err != nil {
return 0, fmt.Errorf("port %q must be numeric", p)
}
return n, nil
} Type guard
func isNumericPort(s string) bool {
n, err := strconv.Atoi(s)
return err == nil && n >= 1 && n <= 65535
} Try / catch
if _, err := strconv.Atoi(portStr); err != nil {
// *strconv.NumError: map service names via getservent/your table,
// or reject with a message that names the offending segment
} Prevention
- Ban service names in generated configs; store ports as integers and stringify only at the CLI boundary.
- Strip '/tcp', '/udp' suffixes and whitespace when ingesting port fields from YAML/env.
- Echo the composed spec in debug output so a mangled port is visible before the dial.
- Validate with strconv.Atoi (not regexes) so your check matches the CLI's exact acceptance.
When it happens
Trigger: Destination `host:https`, `host:` (empty port), `host: 80` (space), or `host:80tcp`. Reached through `invalid remote address: invalid port ...` (-L) or `invalid local address: invalid port ...` (-R).
Common situations: Using IANA service names from muscle memory; copy-paste artifacts like `80,` or `80;`; CI variables containing a port plus protocol suffix (80/tcp); locales/keyboards inserting a non-breaking space.
Related errors
- port 0 is not valid for destination address
- port %d out of range (1-65535)
- start port forwarding: %w
- local port forward %s: %w
- remote port forward %s: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/7bde62869893ac92.
Report an issue: GitHub.