核心路径是ssh.Dial()建连→client.NewSession()获会话→session.Run()或Output()执行命令;client.Do()不存在,HostKeyCallback必须显式设置且Session不可复用。

Go 语言用 crypto/ssh 远程执行命令,核心就一条路:先 ssh.Dial() 建连,再 client.NewSession() 拿会话,最后调 session.Run() 或 session.Output()。别写 client.Do()——它根本不存在,编译直接报错。
HostKeyCallback 必须显式设置,否则连不上
这是最常卡住的第一步。ssh.ClientConfig 的 HostKeyCallback 字段是强制的,不填编译失败;填 ssh.InsecureIgnoreHostKey() 虽能过编译,但生产环境等于放弃主机身份校验。
- 本地调试可用
ssh.InsecureIgnoreHostKey(),CI/CD 或容器中必须换掉 - 推荐用
ssh.KnownHosts(knownHostsFile),路径得自己拼:比如os.UserHomeDir() + "/.ssh/known_hosts",不能直接写~/.ssh/known_hosts - 错误现象
ssh: handshake failed: ssh: unable to authenticate,90% 是HostKeyCallback拒绝了服务端公钥,不是密码或密钥错了——建议先打日志看收到的 key 类型和 fingerprint
每次命令都得新建 *ssh.Session,复用会出问题
*ssh.Session 不是连接池资源,也不是线程安全对象。它是一次性会话,复用会导致 write tcp: broken pipe 或静默丢命令。
- 执行
ls /tmp和df -h,必须分别调两次client.NewSession() -
session.Run("cmd")和session.Output("cmd")都是同步阻塞调用,内部已调Wait(),再手动session.Wait()会 panic - 需要交互(如输密码、响应 yes/no)才用
session.Start()+session.Wait(),且必须配session.StdinPipe()和session.StdoutPipe()
超时控制必须用 context.WithTimeout,ClientConfig.Timeout 不管用
ClientConfig.Timeout 只控制握手阶段,对命令执行完全无效。没加 context 控制,session.Run() 卡死无解。
- 正确写法:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second); defer cancel(); session.Run("sleep 20")—— 10 秒后自动中断 - 别依赖
time.AfterFunc或 goroutine 杀 session,context是唯一可靠方式 - 如果命令本身要长时间运行(如 tail -f),那就不用 timeout,但得确保有明确退出机制
传文件别碰 session.Run("scp"),改用 sftp.Client
用 session.Run("scp file user@host:/path") 看似省事,实则脆弱:远端没 scp、shell 被禁(/bin/false)、路径含空格或中文,全都会崩,且错误模糊难定位。
- 标准做法是走 SFTP 子系统:
sftp.NewClient(sshConn)→client.Create("/remote/path")→io.Copy() - 注意权限:上传后默认是
0600,要用os.FileMode(0644)显式设 - 远端目录不存在?
client.MkdirAll()在旧版 SFTP server 上可能不支持递归,得自己拆路径逐级Mkdir()
真正容易被忽略的是 HostKeyCallback 的行为细节和 Session 的生命周期管理——它们不出现在任何命令行教程里,但一错就导致连接成功却命令不执行、或执行了却没返回、或隔几分钟突然断连。这些不是 bug,是协议层设计使然。


















