自建Web SSH:用Termix把SSH会话搬进浏览器,随时随地远程运维
说个最近的遭遇:人在外面,手机上收到服务器告警,磁盘快满、日志刷屏、进程被挤出。我身边没电脑,手机上的终端客户端在那种情况下根本用不上。折腾了半天,我下决心给自己搭一套能放在浏览器里、随时随地打开就能连的 Web SSH。最后配合 Termix 把 SSH 会话桥接到了网页端,整个过程踩了不少坑。这篇是完整记录:想做这件事的原理、部署方式、安全加固和排错经验都在里面。适合跟我一样经常不在电脑前的人,也适合正在选型的小团队运维同学参考。
1. 为什么我最后选择自建 Web SSH,而不是硬凑合
1.1 传统 SSH 在"移动场景"下的四个尴尬
我不是说命令行 SSH 不好,它在正经办公环境里无可替代。但"随时随地管服务器"这六个字,直接把传统方式的脸打肿了:
第一,客户端依赖。公司电脑、客户现场的机器、朋友借的笔记本,不可能每台都装好 SSH 客户端。遇到 Windows 默认环境更强,命令端都没法直接用。手机端的 SSH 应用倒是不少,可屏幕小、键盘输入难受、复制粘贴还经常乱套。
第二,网络环境限制。很多访客网络只放行 80 和 443 端口,SSH 默认的 22 端口直接被掐死。有的公司出口代理还要审计流量,非标准端口很容易触发告警。
第三,多台机器管理碎片化。我得同时维护好几台服务器,IP、端口、密钥、用户名,光这些信息就够记一堆。没有一个统一的入口,每次都是临时去翻记录,效率极低。
第四,会话稳定性。网络一波动,SSH 会话断掉,正在跑的任务可能跟着遭殃。虽然进程层面有 tmux、screen 顶着,但你在移动端重新连回去的体验依然不痛快。
1.2 Web SSH 的适用人群与场景边界
这套方案不是要取代桌面 SSH,它解决的是"轻量、临时、跨设备"的问题。最适合三类人:
- 个人开发者 / 独立站长:服务器不多,但经常外出,需要快速看一眼服务状态、重启容器、看日志,浏览器就能解决。
- 小团队运维:成员需要共用一个管理入口,但不想给每个人都开服务器系统账号,又想留操作记录。
- 混合环境使用者:公司内网有一批机器,家里有一批机器,想要一个统一的管理页面。
不适合谁?如果你的运维流程已经重度依赖堡垒机、审计系统,或者有严格的等保需求,那就没必要自建,直接走企业级方案。Web SSH 适合"够用就好"的人。
1.3 别人家的方案 vs 自己搭
市面上现成的方案并不少,云厂商控制台里基本都有网页终端,也有在线 SSH 工具网站。但我个人不愿意接受两个限制:一是账号系在特定平台上,换一家云厂商、换一台非公有云机器就用不了;二是中间链路自己不可控,在线工具相当于把服务器会话交给了第三方,安全性我说服不了自己。
Termix 这种自建方案最大的价值就是自主权:会话数据跑在自己控制的链路上,认证规则自己定,日志自己留。虽然搭建花费一些时间,但长期用下来很值。
2. Termix 的工作机制:SSH 是怎么被"塞进"浏览器的
2.1 整体结构:三个角色各司其职
在真正动手之前,我建议先花十分钟理解这套东西的运行逻辑。用一句话概括:浏览器负责显示终端界面,服务端负责代替你完成 SSH 协议通信,两者之间用实时通道连接起来。
拆开看分成三层:
- 前端层:浏览器里跑的终端模拟组件,负责把字符流渲染成一个可交互的终端窗口,同时把键盘输入捕获并发送出去。
- 桥接层:也就是 Termix 服务端本身,接收浏览器的实时连接请求,解析会话指令,然后在服务器本地创建一个真正可用的 SSH 会话。
- 目标层:你要管理的目标服务器,Termix 服务端以普通 SSH 客户端的方式连接它。
整个过程里,浏览器并没有直接跟目标服务器建立 SSH 连接,它只跟 Termix 服务通信。
2.2 一次会话的完整数据流
我画一下我在脑子里理的流程,对这个部分新手容易晕:
- 你在浏览器里打开 Termix 页面,先做登录认证,服务端发给你一个有效的会话令牌。
- 你在页面里输入目标服务器的地址、端口、用户名,或者选择一个预配置的主机。
- Termix 服务端收到请求后,校验权限,然后替你执行一次 SSH 连接。
- 连接建立后,目标服务器的 stdout 输出被编码成数据帧,通过实时通道推给浏览器。
- 浏览器里的终端模拟组件把数据帧还原成文字和颜色,显示在页面上。
- 你在页面敲下的每个按键,也被封装成数据帧回传到服务端,服务端把它写入 SSH 会话的标准输入。
实际用下来,感觉就和本地终端几乎一样。
2.3 为什么要用 WebSocket 而不是普通 HTTP 请求
这里解释一个关键选型点。SSH 会话是双向、持续、实时的通信,每个按键都要很快响应,服务端的输出也要连续推给浏览器。如果用普通 HTTP 轮询,你每敲一个字符就要发一次完整请求,服务端有输出还要另外拉一次,延迟和资源消耗都受不了。
Termix 的桥接通道用的是 WebSocket。它的核心优势是:建立一次连接,之后双向推送数据,不需要反复握手,也不存在短轮询的延迟问题。体验上接近原生 SSH,能做到输入即时回显、输出持续滚动。在反向代理层处理的关键就是把这个 WebSocket 升级请求正确放行,后面第五节我会详细讲踩坑过程。
2.4 会话管理与终端类型协商
还有一个容易被忽略的细节是终端类型协商。传统 SSH 会自动协商一个终端类型,比如xterm-256color,影响颜色显示和快捷键行为。Web SSH 同样要做这个事情,Termix 在建立会话时会带上预设的终端类型,保持颜色主题、光标定位、快捷键组合能正常工作。如果协商不对,最常见的症状是top、htop的界面刷不出来,或者 vim 里的方向键变成字符A、B、C、D。
3. 从零搭起:Termix 部署全流程记录
3.1 部署前的准备工作
先说硬件和环境需求。我用的是一台 1 核 1G 的小机器,CentOS 7 系统,内存小了点,但只跑 Termix 完全够。如果你要同时承载多人使用,建议 2 核 2G 起步。需要一个域名,用来配置 HTTPS,不然浏览器里的部分高级能力(比如剪贴板读取)会被限制。域名解析到这台机器上,并确保 80、443 端口在防火墙里放行。
注意:放行 443 端口之后,如果这台服务器本身就是你的目标机器之一,请确认你没有把 22 端口暴露到公网。Termix 服务本身应当通过系统防火墙只允许本地回环访问,真正对外暴露的只有 443。
3.2 安装与初始化配置
Termix 的安装包跟一般软件不大一样,不同版本的目录结构和命令会存在差异。下面这套步骤是我基于常见实践整理的通用流程,你拿到具体版本后要对照官方说明调整目录和配置文件路径。
# 1. 创建独立运行用户,避免以 root 身份常驻服务 useradd -m -s /bin/bash termix # 2. 切换到该用户并下载安装包,解压到指定目录 su - termix mkdir -p ~/termix tar -xzf termix-release.tar.gz -C ~/termix cd ~/termix # 3. 生成初始配置模板 ./termix init初始化完成后,目录下会有一个termix.yaml配置模板。我个人的习惯是把配置项按三块分:监听地址、认证策略、会话上限。最开始什么都不用想复杂,只改监听地址:
server: listen: "127.0.0.1:8080" # 只监听本地回环 session_timeout: 1800 # 会话空闲超时,单位秒 auth: mode: "password" # 先用密码登录,后边再加 TOTP session_ttl: 7200 # 登录态过期时间用./termix start启动服务,telnet 127.0.0.1 8080能通,就说明基础服务起来了。这时浏览器访问需要先想办法打通外网访问路径,推荐的做法是交给反向代理来处理。
3.3 初始化配置的"可操作"细节
我第一次搭的时候,在配置上犯过一个低级错误:把监听地址配成了服务器公网 IP。这样 Termix 服务本身直接暴露在公网,省掉反向代理的部署,看起来更简单,但后续想加 HTTPS、做访问控制就非常痛苦,而且 8080 端口很容易被扫描工具盯上。正确做法是只监听 127.0.0.1,让外部流量经过反向代理转发进来。这样 Termix 永远只面对本机的 Web 服务器,公网攻击者看不到它。
3.4 反向代理与 HTTPS 配置
我用的主机的 Web 服务器是常见开源方案,给 Termix 加一段反向代理配置。这里有个关键要求:必须放行 WebSocket 升级请求。以我的配置片段为例:
server { listen 443 ssl; server_name term.example.com; ssl_certificate /etc/ssl/term/term.example.com.pem; ssl_certificate_key /etc/ssl/term/term.example.com.key; location /termix/ { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }如果你的 Web 服务器可以自动申请和续期 HTTPS 证书,那更容易,直接把域名指过来,配一个反向代理就好。注意http与https的自动跳转、请求体大小限制要放开,SSH 会话传输的流式数据并不大,不会被 Nginx 限制困扰,但 WebSocket 帧的读取超时要给足。默认超时经常是 60 秒,终端长时间没有输出时会被主动断开,建议把读取超时调大,例如 300 秒以上。
配好后浏览器访问https://term.example.com/termix/,看到登录页,说明整套链路已经通了。
4. 公网暴露的 Web SSH,这些安全加固必须做
4.1 我只跑了两天就被扫描了
这套服务上线后,我特意观察了几天日志。第一天就有来自不同来源的扫描请求,试图猜登录口令。这给我提了一个醒:Web SSH 是价值非常高的攻击目标,因为它一旦被攻破,等于把服务器权限交出去了。所以安全这块不是可选项,是必选项。
攻击面主要有三块:
- 登录前:暴力破解、弱口令尝试、未知路径探测。
- 会话中:令牌泄露、被中间人截获、多用户会话隔离不足。
- 系统层:Termix 服务本身上线后,如果某个未知接口被调用了,安全隐患会放大。
4.2 登录认证升级:从单层密码到两层保护
密码登录在公网上撑不了多久。我的建议是至少做到两层:第一层密码验证,第二层动态验证码。Termix 支持在登录认证基础上挂载动态口令验证,我用的是常见的开源动态口令库,生成方式是基于时间的一次性密码。手机端装一个标准认证类应用,扫描 Termix 生成的二维码,以后每次登录都要输入 6 位动态码。
这样做的好处是:即使密码因为某种原因泄露,没有动态码也进不来。我还顺手做了失败次数限制:同一 IP 连续失败 5 次就临时封禁 15 分钟。这个可以在 Web 服务器层实现,也可以在 Termix 内启用,看各自版本支持情况。
4.3 IP 白名单、访问路径与来源控制
如果你的使用场景相对固定,可以在 Web 服务器层加 IP 限制。只允许公司出口 IP、家里宽带 IP 访问/termix/路径,其它来源一律返回 403。这个策略简单有效,能过滤掉 80% 的扫描流量。
如果活动范围确实不固定,至少可以设置账号级登录地点提醒:登录成功之后,把本次来源 IP 显示在页面上,发现不对立刻手动失效全部会话。
4.4 会话隔离与超时策略
多人共用这套系统时,每个用户的 SSH 会话必须相互隔离。Termix 的每个连接在服务端有独立的会话映射,互不干扰。我配置了空闲超时自动断开,默认为 30 分钟;如果 30 分钟内没有键盘输入,会话自动终止,避免人走开以后会话被接管。
注意:这里的会话超时和系统层的 idle session 不是一回事。系统层的会话可能还会因为服务端线路问题悬挂着,Web SSH 的会话超时只是指浏览器与 Termix 之间的连接。真正要杜绝悬挂连接,建议同时配合使用
ClientAliveInterval这类 SSH 服务端参数。
4.5 审计日志是最容易被忽视的一环
我建议从第一天就开启操作日志。这个日志至少需要记录四个要素:谁、在什么时间、连了哪台机器、执行了什么操作。如果只记录登录成功但没记录具体命令,出问题的时候你连攻击者做了什么都不知道。
Termix 大部分版本可以配置将日志输出成文本落盘。我的做法是直接写到日志目录下,然后定期归档。终端输出和键盘输入如果都记录,日志文件会增长得很快,但如果连续记录两个月,对磁盘占用完全可接受,换来的排查能力非常值得。
| 风险点 | 常见攻击方式 | 我采用的加固措施 |
|---|---|---|
| 登录口令 | 暴力破解、撞库 | 密码 + 动态验证码,失败次数限制 |
| 访问来源 | 全网扫描 | Web 服务器层 IP 白名单,非白名单 403 拒绝 |
| 会话劫持 | 令牌泄露、中间人 | 强制 HTTPS,会话空闲自动过期 |
| 越权操作 | 账号共用、权限过大 | 独立系统账号映射,按需分配主机权限 |
| 事后溯源 | 证据缺失 | 记录登录时间、来源、命令输出、目标主机 |
5. 实测踩坑记录:从能用到好用的关键修复
5.1 复制粘贴失灵和方向键乱码
第一次跑通之后,我用 vim 编辑文件,发现方向键每次都会插入字符A、B、C、D,复制粘贴也完全不受控。一开始以为是 Termix 的问题,排查之后确认是终端类型协商的问题。因为某些终端组件默认以极小型的终端类型连接,老旧的程序不支持功能键转义序列。
解决方式有两种:
- 把终端类型固定为
xterm-256color,在 Termix 的会话配置里指定终端类型参数。 - 检查浏览器页面是否启用了正确的终端模拟组件的键位映射,有些组件默认不开启应用光标模式。
真正把终端类型改成xterm-256color之后,vim 的体验立刻正常了。复制粘贴要注意页面上提供的安全上下文权限,首次使用会要授权剪贴板读取,允许一次后就不再弹。
5.2 WebSocket 被反向代理掐断的完整排查链路
这个问题很典型,值得写出来。上线前一天我测试一切正常,第二天再打开页面,输入密码后能建立会话,但终端没有任何输出,几秒后提示连接已断开。看 Termix 服务端日志,发现浏览器发起 WebSocket 升级请求,服务端已经接受了,但反向代理层没有正确转发。
排查过程是这样的:
- 先用浏览器的开发者工具抓请求,确认 WebSocket 握手请求返回了 200 而不是 101。
- 200 说明反向代理没有把它当一个升级请求,而是当成普通 GET 请求处理了。
- 检查反向代理配置,发现我第一版配置里没有设置
Upgrade和Connection请求头。 - 最初补上了
Connection "upgrade"的配置,接上发现还是不稳,因为代理的读取超时太短,长连接闲置一会儿就被切断。 - 最后调整代理读取超时参数,把超时加大,问题彻底消失。
这里最容易被忽略的是:很多反向代理默认不保留长连接,升级请求一旦走默认处理,WebSocket 就废了。这种问题不是一眼能看出来的,要认真翻一下层层的日志。
5.3 多人同时登录同一主机的会话串扰
有同事在同一时间登录同一台目标服务器,出现了一种奇怪现象:两边敲的命令偶尔会跑到对方会话里。排查后发现,问题不在 Termix 本身,而是我们两个人用的系统账号是同一个。当两个 Web SSH 会话映射到同一个系统用户时,服务端交互式会话会冲突。
解决办法是给每个人分配独立的系统账号,并且 Termix 配置里把账号与主机访问权分开控制。如果只是临时用一下,至少要让每个人手动指定--identity参数连接不同会话,避免复用同一个默认账号。
5.4 手机端体验优化的一些细节
浏览器能开网页就能连服务器,但手机端有几个地方值得打磨:
- 把页面添加到主屏幕,全屏显示,体验接近原生应用。
- 开启页面自带的字号调整选项,手机端字号适中才不费眼。
- 把常用主机预存在 Termix 的配置里,连接时只需要点一下选择,不用敲 IP。
- 开小键盘出来会很占屏,尽量用终端操作,少开输入法。
我实际在手机上用它修过一次配置错误:人在地铁上,服务告警,我打开手机浏览器,点两下进了页面,改掉了配置,服务恢复。以前这种场景基本是等回电脑再说。
一些我最后想分享的使用习惯
Termix 这套体系用了几个月后,我总结了几条自己的习惯。现在每周都会检查一次登录日志,看看有没有陌生 IP 的尝试记录。动态验证码让密码泄露的风险变低,但并不能完全代替定期更换密码。所有目标服务器的密钥尽量都采用密钥登录,并且 Termix 服务端保存密钥文件的位置要设置严格权限,其它用户不可读。如果你准备给团队使用,建议最开始就约定账号命名规范和目标主机分组规则,不然人一多就会乱。
如果你已经有一台可以访问公网的服务器,搭这套东西一两个小时就能搞定,真正花时间的其实是被攻击之后怎么应对。能把这套东西认真跑起来的人,运维效率会提升很大。希望你也能从这套方案里受益。