{"record":{"id":"0723e0fe1f702f01","repo":"netbirdio/netbird","slug":"invalid-port-forward-specification-s-expected-f-0723e0","errorCode":null,"errorMessage":"invalid port forward specification: %s (expected format: [local_host:]local_port:remote_host:remote_port or [local_host:]local_port:unix_socket)","messagePattern":"invalid port forward specification: (.+?) \\(expected format: \\[local_host:\\]local_port:remote_host:remote_port or \\[local_host:\\]local_port:unix_socket\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/cmd/ssh.go","lineNumber":741,"sourceCode":"\tcase 2:\n\t\treturn parseTwoPartForwardSpec(parts, spec)\n\tcase 3:\n\t\treturn parseThreePartForwardSpec(parts)\n\tcase 4:\n\t\treturn parseFourPartForwardSpec(parts)\n\tdefault:\n\t\treturn \"\", \"\", fmt.Errorf(\"invalid port forward specification: %s\", spec)\n\t}\n}\n\n// parseTwoPartForwardSpec handles \"port:unix_socket\" format.\nfunc parseTwoPartForwardSpec(parts []string, spec string) (string, string, error) {\n\tif isUnixSocket(parts[1]) {\n\t\tlocalAddr := \"localhost:\" + parts[0]\n\t\tremoteAddr := parts[1]\n\t\treturn localAddr, remoteAddr, nil\n\t}\n\treturn \"\", \"\", fmt.Errorf(\"invalid port forward specification: %s (expected format: [local_host:]local_port:remote_host:remote_port or [local_host:]local_port:unix_socket)\", spec)\n}\n\n// parseThreePartForwardSpec handles \"port:host:hostport\" or \"host:port:unix_socket\" formats.\nfunc parseThreePartForwardSpec(parts []string) (string, string, error) {\n\tif isUnixSocket(parts[2]) {\n\t\tlocalHost := normalizeLocalHost(parts[0])\n\t\tlocalAddr := localHost + \":\" + parts[1]\n\t\tremoteAddr := parts[2]\n\t\treturn localAddr, remoteAddr, nil\n\t}\n\tlocalAddr := \"localhost:\" + parts[0]\n\tremoteAddr := parts[1] + \":\" + parts[2]\n\treturn localAddr, remoteAddr, nil\n}\n\n// parseFourPartForwardSpec handles \"host:port:host:hostport\" format.\nfunc parseFourPartForwardSpec(parts []string) (string, string, error) {\n\tlocalHost := normalizeLocalHost(parts[0])","sourceCodeStart":723,"sourceCodeEnd":759,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/cmd/ssh.go#L723-L759","documentation":"Returned by parseTwoPartForwardSpec when the spec has exactly two colon parts but the second is not a unix socket path (does not start with / or ./). Two-part specs are only legal as `port:/abs/path` or `port:./rel/path`; a two-part `port:host` form has no meaning because the remote port would be missing, so it is rejected with the full expected-format message.","triggerScenarios":"`-L 8080:localhost peer` (host without port, hoping the default applies), `-R 9000:service peer`, or `-L 8080:socket peer` where the intended socket path lost its leading slash. Any 2-part spec whose second element fails the isUnixSocket prefix check.","commonSituations":"Abbreviating to host-only when the remote port is 'obvious' (db -> 5432); socket paths passed relative without ./ ; typos dropping the port after editing a longer spec down.","solutions":["If the target is TCP, write the full 3-part form: port:host:remote_port (e.g., 8080:localhost:80).","If the target is a unix socket, keep the path absolute (8080:/run/svc.sock) or ./-prefixed so isUnixSocket recognizes it.","Avoid guessing defaults — every destination needs its explicit port or path."],"exampleFix":"# before\nnetbird ssh -L 8080:localhost peer1\n# -> invalid port forward specification: 8080:localhost (expected format: [local_host:]local_port:remote_host:remote_port or [local_host:]local_port:unix_socket)\n\n# after\nnetbird ssh -L 8080:localhost:80 peer1\n# or, for a socket:\nnetbird ssh -L 8080:/var/run/service.sock peer1","handlingStrategy":"validation","validationCode":"// two-part specs are ONLY port + unix socket\nparts := strings.Split(spec, \":\")\nif len(parts) == 2 {\n\tif !strings.HasPrefix(parts[1], \"/\") && !strings.HasPrefix(parts[1], \"./\") {\n\t\treturn fmt.Errorf(\"2-part spec %q needs a socket path as target or a 3rd part (host:port)\", spec)\n\t}\n}","typeGuard":"func isSocketTarget(s string) bool { return strings.HasPrefix(s, \"/\") || strings.HasPrefix(s, \"./\") }","tryCatchPattern":"if len(parts) == 2 && !isUnixSocket(parts[1]) {\n\t// host-only second part: auto-expand to 3 parts with the known service port\n\t// or reject — never guess silently in production paths\n}","preventionTips":["Encode the rule: two colon parts == socket forward, nothing else.","When shortening specs in docs/scripts, always keep host:port together on the destination side.","Keep socket paths absolute (/run/svc.sock) so the prefix check recognizes them.","Lint configs for 2-part forwards whose second part lacks a leading slash."],"tags":["go","ssh","port-forwarding","validation","cli"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}