前几天部署了 DSH 的手机访问链路:DeepSeek Harness 跑在电脑上,手机浏览器通过云服务器中转也能随时打开。本文记录这套方案的骨架、安全设计和踩过的坑,给同样想”把本机工具搬进手机”的朋友一份参考。
为什么需要中转
DeepSeek Harness(下文简称 DSH)默认只监听本机回环地址,这是安全设计——一个跑在本地、能读写文件与配置的助手,不应该裸奔在公网上。但它也带来一个问题:离开电脑就访问不到了。
解决思路很朴素:**让一台云服务器做”中立中转站”**——电脑把 DSH 的端口通过 SSH 反向隧道”推到”服务器的回环地址上,手机访问服务器域名时,服务器再把请求原路送回电脑。全程只有一条保持连接的加密隧道,服务器上不会存留任何会话数据。
架构
手机浏览器
│ https://<桌面入口子域名> / https://<移动网关子域名>
▼
云服务器 nginx (443/HTTP2 + 自动续期证书)
│ 反向代理 -> 127.0.0.1:3080 / :3443
▼
SSH 反向隧道 (电脑 -> 服务器,常驻)
│
▼
电脑上的 DSH Web (仅监听 127.0.0.1)
整条链路就三块拼图:电脑端的 DSH(只监听自己的回环地址,是”中心”)、服务器端的 nginx(唯一暴露在公网的门面)、连接二者的 SSH 反向隧道(负责搬请求,全程加密)。
下面按搭建顺序一步一步来。每一步都说明:做什么、用什么命令、踩过什么坑。
分步搭建
第 1 步:准备云服务器与域名
做什么:一台有公网 IP 的云服务器(个人使用最低配就够,这里只做转发)+ 一个域名,解析好两个子域名:桌面入口和移动网关。
命令:
# 服务器网络是否可达(ICMP 常被云厂商禁 ping,用 HTTP 探测更靠谱)
curl -v http://<服务器IP>
# 域名解析是否生效
nslookup <桌面入口子域名>
坑:
- 云厂商的”安全组/防火墙”和系统内的防火墙(ufw/firewalld/iptables)是两层,必须都放行才算数——只开一层,症状是”外面访问不通,服务器自己也连不上”;
- 解析线路类型这个坑很隐蔽:看着 A 记录明明存在,访问却总被导到别处、证书也签不下来,最后发现是解析商的线路类型(默认/境外线路)把路引到了非预期节点。我当时就是没查这一项,排查了好久。动手前务必先把解析验证好。
第 2 步:服务器基础加固
做什么:SSH 改用非标准端口 + 密钥登录、禁用密码认证、限制认证次数,装 fail2ban 盯 sshd(后面再加 nginx)。先做这一步,因为后面所有操作都在这台机器上,先把暴露面收住。
命令:
# 本机生成密钥(一次即可)
ssh-keygen -t ed25519
# 上传公钥(Windows 下用 scp 同样可以)
scp ~/.ssh/id_ed25519.pub 用户@<服务器IP>:~/.ssh/authorized_keys
# 服务器端修改 SSH 配置
sudo vi /etc/ssh/sshd_config
# Port <改成的非标准端口>
# PasswordAuthentication no
# MaxAuthTries 3
sudo systemctl restart sshd
# fail2ban(Debian/Ubuntu 示例)
sudo apt install fail2ban
坑:
- 改 SSH 端口后,云安全组要同步放行新端口,否则下一步就把自己锁在门外;
- 改配置之前,先另开一个终端验证密钥能登录,确认无误再断开旧会话——经典的自锁事故。
第 3 步:nginx 反向代理与 HTTPS 证书
做什么:装 nginx,建两个 server 块——桌面入口反代到 127.0.0.1:3080(加 Basic Auth),移动网关反代到 127.0.0.1:3443(改写 Host/Origin);然后用 Let’s Encrypt 自动签发并续期证书。
命令(配置模板,域名自行替换):
# /etc/nginx/sites-available/dsh-desktop —— 桌面入口
server {
listen 443 ssl http2;
server_name <桌面入口子域名>;
ssl_certificate /etc/letsencrypt/live/<桌面入口子域名>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<桌面入口子域名>/privkey.pem;
auth_basic "restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://127.0.0.1:3080;
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;
}
}
# /etc/nginx/sites-available/dsh-mobile —— 移动网关
server {
listen 443 ssl http2;
server_name <移动网关子域名>;
ssl_certificate /etc/letsencrypt/live/<移动网关子域名>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<移动网关子域名>/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3443;
# 网关会校验 Host/Origin,必须改写成自己的域名,否则握手直接被拒
proxy_set_header Host $host;
proxy_set_header Origin $scheme://$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;
}
}
证书与 Basic Auth 密码:
sudo apt install certbot python3-certbot-nginx
# HTTP-01 校验:要求 80 端口可达、域名已解析到本机
sudo certbot --nginx -d <桌面入口子域名> -d <移动网关子域名>
sudo systemctl enable certbot.timer # 自动续期
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd <用户名> # Basic Auth 密码
坑:
- 证书签不下来,十有八九是:80 端口没放行,或解析没到这台服务器(回看第 1 步);
- Host/Origin 头不匹配是深层坑:DSH 移动网关会校验请求的
Host/Origin,直接代理转发(不改写)时请求头还是内部地址,握手直接失败。排查顺序建议先确认proxy_set_header Host $host与Origin $scheme://$host都写了,大部分握手问题就消失了; - 两个子域名一次签发、一份证书覆盖即可,证书自动续期后全链路无感,不要去手动换证书引入新错误。
第 4 步:电脑端 SSH 反向隧道
做什么:在电脑上建立常驻反向隧道,把本地 3080 和 3443 两个端口推到服务器的回环地址,并配置保活与自动重连。
命令(以 Linux/Mac 为例;Windows 下用编辑器直接改 C:\Users\<你>\.ssh\config,效果相同):
# ~/.ssh/config 里给服务器起个别名,以后一条命令即可
Host dsh-cloud
HostName <服务器IP>
Port <SSH端口>
User <用户名>
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 30
ServerAliveCountMax 3
ExitOnForwardFailure yes
# 建立反向隧道(常驻前台)
ssh -N -R 127.0.0.1:3080:127.0.0.1:3080 \
-R 127.0.0.1:3443:127.0.0.1:3443 \
dsh-cloud
做成常驻:Windows 用计划任务(开机启动、失败重启)或 winsw/nssm 把上述命令包装成系统服务;Linux/Mac 用 autossh 更省心(autossh -M 0 -N -R 127.0.0.1:3080:127.0.0.1:3080 -R 127.0.0.1:3443:127.0.0.1:3443 dsh-cloud)。
坑:
- 服务器端 sshd 的
AllowTcpForwarding若被改成no(部分厂商镜像改过),隧道”成功”但端口根本不会出现在服务器上——用ss -tlnp | grep -E '3080|3443'验证一下最直接; - **务必写
-R 127.0.0.1:3443:...而不是-R 3443:...**:后者的远端端口会绑在0.0.0.0上,等于把移动网关直接暴露到公网,违背安全设计; - 加
ExitOnForwardFailure yes:服务器上若有残留端口占用,隧道会立刻报错退出,而不是假装正常挂着; - Windows 的 OpenSSH 对私钥文件权限敏感,报 “bad permissions” 时用
icacls收一下权限,只留当前用户; - 网络切换(如宿舍↔家)会导致连接挂起,
ServerAliveInterval保活 + 服务自动重启能把这种情况兜住。
第 5 步:移动端网关(dsh-mobile)
做什么:给 DSH 装社区插件 dsh-mobile(开源仓库 saya-ch/dsh-mobile,Apache-2.0,通过 npm 安装)作为移动端网关,再配对手机。
操作:
npm install dsh-mobile # 按插件文档接入 DSH
# 手机浏览器打开 https://<移动网关子域名> → 进入配对页
# 电脑端开配对窗口(一次性令牌,10 分钟内有效):
curl -X POST http://127.0.0.1:3443/api/mobile-access/pairing/open -d '{}'
# 手机输入令牌 → 配对完成;设备记录存在 ~/.dsh/mobile-access/devices.json
信任模型要记牢:已配对的设备 = 完全信任。所以网关本身只监听回环地址、只有公网域名才放开配对入口、配对窗口只能从电脑端发起。设备记录有效期 90 天,会话 8 小时,且会话在网关内存态——网关重启后手机要重新授权(重绑时先回电脑端重开配对窗口)。
版本兼容上的小工作:dsh-mobile 的启动校验只对较早的 DSH 版本放行(官方验证到 0.1.0-rc.7),遇到更新的版本会直接拒绝启动;而我本机使用的 DSH 是 0.1.1-rc.2。本地适配只做了两处几行的小改动:一是把版本白名单补齐到当前使用的 rc.2;二是适配新版前端启动脚本中全局变量声明书写形式的变化(新版本改用了 globalThis 写法)。两处补丁都已整理成建议反馈给插件作者,等上游更新合并后即可去掉。
坑:
- 网关启动被拒,先看日志区分是”版本校验”还是”端口占用”——前者按上面补丁处理,后者检查 3443 是否被其他进程占用;
- 本地调试直接访问
127.0.0.1:3443时,请求头必须携带网关认可的Host/Origin(也就是走 nginx 那条链路、用域名访问),裸地址直连会被校验拒掉——所有访问都走域名,本地调试也一样。
第 6 步:联动验收
做什么:按清单走一遍,确认整条链路成立、自愈能力可用。
# 服务器端:隧道端口是否在监听(理想输出应只有 127.0.0.1 绑定)
ss -tlnp | grep -E '3080|3443'
# 证书状态
sudo certbot certificates
- 浏览器打开
https://<桌面入口子域名>:应弹 Basic Auth,输入后进入 DSH 桌面界面; - 手机打开
https://<移动网关子域名>:配对 → 正常使用,移动端布局与交互正常; - 自愈测试:电脑重启 / 切换网络后,隧道应自动恢复;服务器重启后,重连脚本应把隧道拉回来。
安全设计(硬底线)
这套方案把”方便”和”安全”做了明确切割:
| 层 | 措施 |
|---|---|
| 公网暴露面 | 云防火墙只放行 80/443/SSH;SSH 改用非标准端口 |
| SSH | 仅密钥登录、禁止密码、限制最大认证次数,配合 fail2ban 自动封禁扫描 |
| Web | 桌面入口加 Basic Auth;移动网关仅回环监听 + 域名校验;fail2ban 盯非 2xx 状态码 |
| 证书 | 全自动签发/续期,几个域名一条龙 |
| 监控 | nginx 日志按域名拆分;每周一 08:00 自动生成安全运维周报发到邮箱(登录失败、封禁统计、证书剩余天数、资源占用) |
一句话:公网只暴露”门面”,真正干活的服务永远只在回环地址上;手机侧访问请求后也无任何数据落盘在服务器。
使用体验与踩坑
手机第一次访问
打开移动网关子域名页面 → 配对页面 → 在电脑端开配对窗口(一次性令牌)→ 手机输码 → 完成。之后就是正常的主界面,移动端布局、键盘交互都调过。
两个容易误会的现象
- **网关重启后手机上”要重新授权”**:网关的会话是内存态,重启就要重新配一次对。这不是 bug,是设计——也意味着服务器/隧道恢复后手机要重绑,重绑前记得先在电脑端重开配对窗口。
- 打开大历史会话时报”用户中止了请求”:手机上每次打开会话都会全量回放事件日志,几 MB 级别的重会话会明显吃力,浏览器干脆掐断了请求。结论是:重会话只在电脑端开,手机上开轻量会话/新会话。
其他细节
- 插件的 3 秒轮询(扩展清单、自定义样式)是插件自身设计,移动端首屏慢多半在插件客户端模块下载,不是网络故障;
- 隧道断了会自动重连,但网关会话重置时需要手机重绑,出远门前建议在电脑端保持隧道常驻。
小结
这套方案的成本:一台任意廉价云服务器 + 一个域名 + 一个周末下午的配置时间,换来的是”自己的助手跟着自己走”。安全性上,只要坚持”服务不进公网、入口全部代理+鉴权+自动封禁”这三条,个人使用足够放心。
如果你也想要同款,欢迎交流:关于页留了我的联系方式。