轻量云服务器未必适合承载本地大模型,却足以运行 Agent 框架并通过 API 调用模型。将 DeepSeek 的 dsh 开放给外网使用时,真正棘手的不只是安装,还包括进程守护、反向代理、身份认证、WebSocket 连接和权限边界。下面从实际部署链路出发,逐项还原配置与排障过程。
4 核 4G 的轻量服务器跑不动本地大模型,但跑 Agent 框架 + API 调模型绰绰有余。本文记录我把 DeepSeek 官方 Agent 框架 dsh 部署到外网可访问的全过程。
最终架构长这样(三层安全是灵魂:dsh 只听本机回环、Nginx 层 Basic Auth、防火墙只开 22/80,3080 永不出公网——因为 dsh 的 Agent 能读写文件、执行 shell,裸暴露等于把服务器钥匙挂门上):
浏览器 ──http:80──▶ Nginx(Basic Auth + WebSocket 透传)
│ proxy_pass(仅本机回环)
▼
dsh(127.0.0.1:3080,browser-trust fence)
│ HTTPS + API Key
▼
DeepSeek API(deepseek-chat)
通用操作,命令按序执行:
# SSH 登录(腾讯云新版镜像默认用户是 ubuntu 不是 root)并切 root
ssh ubuntu@你的服务器IP
sudo -i
# 系统更新 + Node 22(插件生态普遍要求 22.x,别装 18)
apt update && apt upgrade -y
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs
npm config set registry https://registry.npmmirror.com # 国内镜像源
# 装 dsh,配成 systemd 常驻服务(解决断连即停、重启不自启)
npm install -g @deepseek-ai/dsh
which dsh # 记下路径,写进下面 ExecStart
# /etc/systemd/system/dsh.service(用 nano 编辑,别用 vi)
[Unit]
Description=DeepSeek Harness Web UI
After=network.target
[Service]
ExecStart=/usr/bin/dsh web
Restart=always
RestartSec=5
User=root
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
systemctl daemon-reload && systemctl enable --now dsh
systemctl status dsh # active (running) 即成功
curl -I http://127.0.0.1:3080 # 401 也算通(token 鉴权在工作)
接模型:去 platform.deepseek.com 充 10 块钱、创建 API Key(只显示一次)。写 ~/.dsh/settings.yaml:
agent-default-model:
provider: deepseek
model: deepseek-chat
llm-pi-ai:
providers:
deepseek:
api: openai-completions
baseURL: https://api.deepseek.com/v1
apiKeyEnv: DEEPSEEK_API_KEY # Key 不落盘,从环境变量读
models:
- id: deepseek-chat
- id: deepseek-reasoner
再 systemctl edit --full dsh,[Service] 段加 Environment=DEEPSEEK_API_KEY=sk-xxx,daemon-reload + restart。到这里 dsh 在本机 3080 跑着了,但外网摸不到——这是刻意的安全设计。
新手粘贴三连坑(我都踩过):两条命令挤一行 → 报
cannot execute binary file,一次一条,见提示符再敲下一条;PowerShell 粘 heredoc 带^[[200~特殊字符 → 卡在>,Ctrl+C 后改用 nano;vi 卡在-- INSERT --→ 直接用 nano(Ctrl+O 保存、Ctrl+X 退出)。
现在套上 Nginx 这层"传达室"。
apt install -y nginx apache2-utils
htpasswd -c /etc/nginx/.htpasswd admin # 输两遍密码
坑 1:
htpasswd: cannot create file /etc/nginx/.htpasswd我第一反应是命令错了,其实原因是
/etc/nginx/目录不存在——nginx 包压根没装上(apt 那步没执行成功)。htpasswd 能跑只代表apache2-utils装了,两个包是独立的。回去补apt install -y nginx即可。
cat > /etc/nginx/sites-available/dsh <<'EOF'
server {
listen 80;
server_name _;
auth_basic "dsh-login";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://127.0.0.1:3080;
# WebSocket 三件套(流式输出必需,缺了发消息没反应)
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;
# Agent 一轮可能跑几分钟,默认 60s 超时会中途断
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
EOF
ln -s /etc/nginx/sites-available/dsh /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx
整段 heredoc 必须一次性粘贴,结尾
EOF单独成行、顶格。
浏览器访问 http://服务器IP,弹完密码框后给我一个 500。
定位三步:
tail -n 30 /var/log/nginx/error.log # 灵魂命令,错误原因都在这
systemctl status dsh # 确认后端活着
journalctl -u dsh -n 50 --no-pager # 看后端日志
error.log 里赫然写着:
open() "/etc/nginx/.htpasswd" failed (13: Permission denied)
原因:我 chmod 640 之后,文件属主 root、属组 root。而 nginx 是 master(root) + worker(www-data) 的进程模型——绑 80 端口的是 root master,但实际处理请求、读密码文件的是降权后的 www-data worker,它不在 root 组里,读不了。
一行修复:
chown root:www-data /etc/nginx/.htpasswd
systemctl reload nginx
通用教训:给 nginx 的文件,权限要对着 www-data 配,不是对着 root 配。排障心法:tail error.log > 猜。
权限修好后,浏览器又给我一句:
dsh web authentication required; reopen the URL printed by dsh web.
这不是 nginx 的锅(502/500 才是),是 dsh 自己的 Host 校验在拦人。dsh 像 Vite/Jupyter 一样内置了"浏览器信任围栏":只信任 127.0.0.1/localhost 的请求,防止外网直连调它的敏感 API。而走反代时,dsh 看到的 Host 是你的公网 IP——不认识,拒。
dsh web --help 里找到了正解:
--trusted-host <authority...> extra authority the /api browser-trust fence accepts
改 systemd 服务(systemctl edit --full dsh):
ExecStart=/usr/bin/dsh web --no-open --trusted-host 你的服务器IP
systemctl daemon-reload && systemctl restart dsh
刷新浏览器 → 弹密码框 → 输密码 → dsh 界面出来了。手机切流量再试一次,真·外网可访问。
附带一个小坑:dsh 启动日志里的 token URL 会被 systemd 自动省略成
...nvQbJ9k。想看完整内容加-l:journalctl -u dsh --no-pager -l | grep token=。日志末尾的>是截断标记,不是 token 的一部分。token 等同于第二把钥匙,别截图公开。
dsh plugin --profile web add dsh-dream-skin
坑 4:报
pnpm not found on PATH——dsh 用 pnpm 管理插件,先npm install -g pnpm。坑 5(最哭笑不得):装完 pnpm 再跑,报
ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER。pnpm 报错原文:"dsh-dream-skinroot@VM-0-2-ubuntu:~#" isn't supported by any available resolver.包名末尾粘着终端提示符
root@VM-0-2-ubuntu:~#——复制命令时把提示符一起带进去了。SSH 客户端粘贴前,看一眼命令末尾干不干净。
装完插件记得 systemctl restart dsh——systemd 拉起的进程不会自动感知新装的包。
两个学习神器:
dsh --profile web --dump-config # 打印实际加载的整棵配置树
journalctl -u dsh -f # 实时日志
改配置前后各 dump 一次做对比,是理解"一切皆插件"最快的方式。
| # | 现象 | 原因 | 解法 |
|---|---|---|---|
| 1 | htpasswd cannot create file | nginx 没装上 / 路径打错 | 补装 nginx,逐字核对路径 |
| 2 | 500 Internal Server Error | www-data 读不到 .htpasswd | chown root:www-data |
| 3 | dsh authentication required | browser-trust fence 拦截 | --trusted-host 你的IP |
| 4 | pnpm not found | 插件依赖 pnpm | npm install -g pnpm |
| 5 | ERR_PNPM_SPEC... | 提示符粘进包名 | 粘贴前检查命令末尾 |
| 6 | 插件装完没生效 | systemd 不自动重载 | systemctl restart dsh |
| 7 | 命令报奇怪参数错误 | 两条命令挤一行/粘贴污染 | 一次一条,见提示符再敲 |
| 8 | heredoc 卡在 > | PowerShell 粘贴带特殊字符 | Ctrl+C,改 nano |
| 9 | 换域名访问又被 fence 拦 | trusted-host 只写了 IP | 域名也加进 --trusted-host(可重复) |
本文为了聚焦踩坑主线,用的是学习环境的最低配置。两个已知妥协,按优先级给出升级路径:
| 妥协点 | 风险 | 升级方案 |
|---|---|---|
| 全站 HTTP | Basic Auth 只是 Base64 编码不是加密,公网传输的密码、对话内容可被中间人截获 | 上 HTTPS:有域名一条命令搞定(apt install certbot python3-certbot-nginx && certbot --nginx,自动配 443 和 80 跳转);没域名用 Tailscale 或 Cloudflare Tunnel 组私有入口 |
| dsh 以 root 运行 | Agent 被诱导执行恶意命令 = 整机 root 失守 | 建专用用户:useradd -m dsh,unit 改 User=dsh 并加 ProtectSystem=strict;注意配置目录随之变成 /home/dsh/.dsh,装插件也要用同一用户跑 |
另外三条低成本加固:
走完这篇,你得到的是:一台外网可访问、有密码保护、开机自启的 DeepSeek Agent 服务器,成本 = 服务器钱 + API 充值的 10 块钱。
比结果更值钱的是过程中建立的三层思维:
error.log、journalctl -l、--dump-config,三个命令看穿一切进阶方向:按《安全升级清单》加固,然后玩 dsh 的会话回放(resume/fork/replay)。
环境备注:腾讯云轻量 4C4G / Ubuntu 24.04 / Node v22 / dsh + pnpm 12.x,2026-09 实测通过。dsh 迭代很快,包名、配置字段以官方仓库 README 为准。
tlwr886n路由器怎么进入设置(tlwr886n路由器进入设置方法)
tlwdr5620易展版怎么恢复出厂设置( tlwdr5620易展版恢复出厂设置方法)
Codex VS Code 插件更新后为何出现空白窗口?
Codex VS Code 插件为何报错“app-server process exited with code 1”?
tlwdr5620管理员密码怎么修改(tlwdr5620管理员密码修改方法)
Codex VS Code 插件在 Ubuntu 中为何发生 Webview 加载故障?