ssh.clientconn 不能直接执行命令,必须通过 ssh.client:ssh.dial → ssh.client → client.newsession() → session.run();认证失败多因密钥格式(不支持 openssh 格式)或签名算法(如 rsa-sha1 被禁用)不匹配;密码认证需用 ssh.password 类型;session.run() 必须检查返回 err,非零退出码需用 *ssh.exiterror 断言获取;交互命令需 requestpty 并显式设置 stderr 和 signal。

ssh.ClientConn 不能直接用,得走 ssh.Client
很多人卡在第一步:调用 ssh.NewClientConn 后拿到的是底层连接,没有封装命令执行逻辑。它只负责握手和加密通道,不提供 Session 或 Run 方法。真正能执行命令的是 ssh.Client —— 它包装了连接,并支持创建会话。
正确路径是:ssh.Dial → ssh.Client → client.NewSession() → session.Run()。跳过 ssh.Client 直接操作底层连接,后续所有命令、stdin/stdout/stderr 处理都得自己实现,极易出错。
认证失败常见于 key 格式或签名算法不匹配
错误信息如 ssh: unable to authenticate, attempted methods [none publickey] 或 signing failed: no supported signature algorithm,通常不是密码错了,而是密钥格式或算法被服务端拒绝。
-
ssh.ParsePrivateKey只支持 PEM 封装的 PKCS#1(老式-----BEGIN RSA PRIVATE KEY-----)或 PKCS#8(-----BEGIN PRIVATE KEY-----),不支持 OpenSSH 格式(-----BEGIN OPENSSH PRIVATE KEY-----);遇到后者需先用ssh.ParseRawPrivateKey(Go 1.19+) - OpenSSH 8.8+ 默认禁用
rsa-sha1签名,而旧版 Go 的ssh.ParsePrivateKey对 RSA 密钥默认生成该签名。解决方法是显式用ssh.SignerFromKey+ 指定ssh.KeyAlgoRSA或升级到 Go 1.20+,它会自动选rsa-sha2-512 - 密码认证需传
ssh.Password类型的Auth,不是字符串;且服务端必须开启PasswordAuthentication yes
session.Run() 阻塞直到远程命令退出,别漏掉 error 检查
session.Run("ls -l /tmp") 看似简单,但它的返回值才是命令真实退出状态。很多人只读 stdout 就认为成功了,结果命令实际已失败(比如权限不足、路径不存在),却没感知。
-
session.Run()返回err,若非 nil,说明命令执行失败或连接中断;即使 stdout 有输出,也要检查这个err - 若需区分退出码,用
err.(*ssh.ExitError)类型断言,再调.ExitStatus();注意:非零退出码本身不触发 panic,只是普通 error - 避免用
session.Output()替代Run()—— 它内部就是 Run + ReadAll,但掩盖了错误来源;调试时更难定位是命令失败还是读取超时
长时命令或交互场景必须显式设置 session.Signal 和 session.Stderr
执行 top、tail -f 或需要响应 stdin 的命令时,session.Run() 会一直阻塞,且默认不暴露 stderr 流,导致错误信息丢失或死锁。
- 启用伪终端(pty):调
session.RequestPty("xterm", 80, 40, ssh.TerminalModes{}),否则很多交互命令直接退出 - 显式绑定
Stderr:用session.Stderr = os.Stderr或自定义io.Writer,否则 stderr 输出会被丢弃 - 处理中断信号:若用户按 Ctrl+C,本地要发
session.Signal(ssh.SIGINT),否则远程进程收不到 - 超时控制别依赖
context.WithTimeout包裹session.Run()—— 它只中断 Go 协程,不杀远程进程;应配合session.Setenv("TIMEOUT", "30")或在命令里加timeout 30s
最易忽略的是:哪怕只是执行一条 sleep 10,如果没设 pty 且远程 shell 是 zsh/bash 的交互模式,也可能因缺少 tty 而提前退出或挂起。是否需要 pty 不由你写的命令决定,而由远程 shell 的配置决定。











