{"record":{"id":"d673d8d09e96c638","repo":"slackhq/nebula","slug":"sshd-listen-can-not-use-port-22","errorCode":null,"errorMessage":"sshd.listen can not use port 22","messagePattern":"sshd\\.listen can not use port 22","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ssh.go","lineNumber":92,"sourceCode":"\t})\n}\n\n// configSSH reads the ssh info out of the passed-in Config and\n// updates the passed-in SSHServer. On success, it returns a function\n// that callers may invoke to run the configured ssh server. On\n// failure, it returns nil, error.\nfunc configSSH(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) (func(), error) {\n\tlisten := c.GetString(\"sshd.listen\", \"\")\n\tif listen == \"\" {\n\t\treturn nil, fmt.Errorf(\"sshd.listen must be provided\")\n\t}\n\n\t_, port, err := net.SplitHostPort(listen)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid sshd.listen address: %s\", err)\n\t}\n\tif port == \"22\" {\n\t\treturn nil, fmt.Errorf(\"sshd.listen can not use port 22\")\n\t}\n\n\thostKeyPathOrKey := c.GetString(\"sshd.host_key\", \"\")\n\tif hostKeyPathOrKey == \"\" {\n\t\treturn nil, fmt.Errorf(\"sshd.host_key must be provided\")\n\t}\n\n\tvar hostKeyBytes []byte\n\tif strings.Contains(hostKeyPathOrKey, \"-----BEGIN\") {\n\t\thostKeyBytes = []byte(hostKeyPathOrKey)\n\t} else {\n\t\thostKeyBytes, err = os.ReadFile(hostKeyPathOrKey)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error while loading sshd.host_key file: %s\", err)\n\t\t}\n\t}\n\n\terr = ssh.SetHostKey(hostKeyBytes)","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/ssh.go#L74-L110","documentation":"The SSH server refuses to bind to port 22 because the host's real sshd already owns it; configSSH rejects any sshd.listen whose port string equals \"22\". This forces operators to choose an alternate port so the built-in SSH server doesn't collide with the system one.","triggerScenarios":"Setting sshd.listen = \"0.0.0.0:22\" or \":22\"; the literal port comparison `port == \"22\"` in ssh.go's configSSH fires after a successful SplitHostPort.","commonSituations":"Operators keeping the default SSH port out of habit; templated configs that substitute the standard port; misreading the docs that this SSH server is supplementary and must use a non-22 port.","solutions":["Change sshd.listen to a non-22 port, e.g. \"0.0.0.0:2222\".","Update deployment templates/Ansible vars that pin the port to 22.","Port-forward from 22 externally if clients require the standard port."],"exampleFix":"// before\nsshd.listen = \"0.0.0.0:22\"\n// after\nsshd.listen = \"0.0.0.0:2222\"","handlingStrategy":"validation","validationCode":"raw := cfg.GetString(\"sshd.listen\", \"\")\nif _, port, err := net.SplitHostPort(raw); err == nil && port == \"22\" {\n    return errors.New(\"sshd.listen cannot use port 22; choose e.g. 2222\")\n}","typeGuard":null,"tryCatchPattern":"run, err := configSSH(logger, srv, c)\nif err != nil {\n    if strings.Contains(err.Error(), \"can not use port 22\") {\n        logger.Error(\"sshd.listen uses reserved port 22; pick an alternate port\")\n        os.Exit(78)\n    }\n    return err\n}","preventionTips":["Reserve a nonstandard default (2222) in templates and docs.","Add the port-22 check to your CI config lint step.","If external clients need port 22, front the server with a TCP forwarder instead."],"tags":["ssh","configuration","port-conflict"],"backgroundTag":"port-22-reserved","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}