{"record":{"id":"5eba596c888af574","repo":"henrygd/beszel","slug":"ssh-disabled","errorCode":null,"errorMessage":"SSH disabled","messagePattern":"SSH disabled","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/server.go","lineNumber":38,"sourceCode":"\t\"github.com/fxamacker/cbor/v2\"\n\t\"github.com/gliderlabs/ssh\"\n\tgossh \"golang.org/x/crypto/ssh\"\n)\n\n// ServerOptions contains configuration options for starting the SSH server.\ntype ServerOptions struct {\n\tAddr    string            // Network address to listen on (e.g., \":45876\" or \"/path/to/socket\")\n\tNetwork string            // Network type (\"tcp\" or \"unix\")\n\tKeys    []gossh.PublicKey // SSH public keys for authentication\n}\n\n// StartServer starts the SSH server with the provided options.\n// It configures the server with secure defaults, sets up authentication,\n// and begins listening for connections. Returns an error if the server\n// is already running or if there's an issue starting the server.\nfunc (a *Agent) StartServer(opts ServerOptions) error {\n\tif disableSSH, _ := utils.GetEnv(\"DISABLE_SSH\"); disableSSH == \"true\" {\n\t\treturn errors.New(\"SSH disabled\")\n\t}\n\tif a.server != nil {\n\t\treturn errors.New(\"server already started\")\n\t}\n\n\tslog.Info(\"Starting SSH server\", \"addr\", opts.Addr, \"network\", opts.Network)\n\n\tif opts.Network == \"unix\" {\n\t\t// remove existing socket file if it exists\n\t\tif err := os.Remove(opts.Addr); err != nil && !os.IsNotExist(err) {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// start listening on the address\n\tln, err := net.Listen(opts.Network, opts.Addr)\n\tif err != nil {\n\t\treturn err","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/henrygd/beszel/blob/b38fb7dafa60812cc22e6a84ce313e94f1ce0a32/agent/server.go#L20-L56","documentation":"StartServer returns 'SSH disabled' when the DISABLE_SSH environment variable is set to 'true'. This allows running the agent purely in push/poll mode without its embedded SSH server. It is a deliberate configuration guard, not an unexpected failure.","triggerScenarios":"Agent.StartServer is called while the environment contains DISABLE_SSH=true; the check runs before any socket binding, so it fires immediately on startup.","commonSituations":"Operator intentionally disabled the SSH server for security hardening but the hub is still configured to connect to the agent over SSH; a stale DISABLE_SSH=true left in a container env or systemd unit after switching connection modes.","solutions":["Remove DISABLE_SSH=true from the agent's environment (env file, systemd unit, docker-compose) and restart","If the hub uses WebSocket connection mode, configure the system in the hub accordingly so it doesn't need SSH","Set DISABLE_SSH to any value other than 'true' (or unset it)"],"exampleFix":"// before (docker-compose)\nenvironment:\n  - DISABLE_SSH=true\n// after\nenvironment:\n  - KEY=... # DISABLE_SSH removed","handlingStrategy":"validation","validationCode":"// before starting the agent\nif v, _ := utils.GetEnv(\"DISABLE_SSH\"); v == \"true\" && hubUsesSSHConnection {\n    log.Fatal(\"hub connects via SSH but DISABLE_SSH=true\")\n}","typeGuard":null,"tryCatchPattern":"if err := agent.StartServer(opts); err != nil {\n    if err.Error() == \"SSH disabled\" {\n        log.Info(\"SSH intentionally disabled; running in push mode\")\n        return nil\n    }\n    return err\n}","preventionTips":["Keep DISABLE_SSH unset unless you exclusively use WebSocket connections","Audit env files/systemd units for leftover DISABLE_SSH=true","Document the connection mode per deployment"],"tags":["ssh","configuration","env-var","startup"],"backgroundTag":"feature-disabled-by-env-var","analyzedSha":"b38fb7dafa60812cc22e6a84ce313e94f1ce0a32","analyzedAt":"2026-08-31T15:10:10.149Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}