SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

如何从笔记本电脑访问 VPS 上的 DSH Web UI

DSH Web UI 默认绑定 127.0.0.1:3080,VPS 打印的 URL 无法直接在笔记本打开。本文介绍 3 种安全访问方式,并说明应避免的做法。

为什么笔记本电脑无法打开 DSH Web UI

DSH Web UI 绑定到 loopback 接口,因此终端打印的 URL 只能在打印该 URL 的计算机上使用。npx @deepseek-ai/dsh web 报告的启动 URL 形如 http://127.0.0.1:3080/?token=...,而对读取该地址的计算机来说,127.0.0.1 表示“读取该地址的计算机”。您的笔记本电脑读取该地址后,会检查自身的 loopback 接口,但那里没有任何进程监听。如果不清楚的是地址本身,请参阅为什么 dsh 一开始会打印 http://127.0.0.1:3080,其中有更详细的说明。不要更改 DSH 的绑定地址。请改为从笔记本电脑建立一条经过身份验证的路径,连接到 VPS 的 loopback 地址。

loopback 默认设置是正确的,本页介绍的所有方法都会保留该设置。端口 3080 后面运行的是一个可在服务器上执行 shell 命令的代理,而 Web UI 不提供登录页面。DSH 现在会通过启动令牌对浏览器进行身份验证:每次进程启动时都会生成一个新令牌,打印的 URL 会携带该令牌,首次访问时会将其交换为会话 Cookie。该令牌是放在 URL 中的单一持有者凭据,因此 loopback 仍是最重要的安全层:要访问该套接字,您必须先在服务器上获得 shell。

DSH 绑定的地址以及如何检查自己的配置

截至 11 October 2026,DeepSeek Harness README 写明,npx @deepseek-ai/dsh web“默认在 http://127.0.0.1:3080 启动 Web UI”。同一仓库中的 CLI 参考文档说明,默认端口仍为 3080;--host“必须指定一个具体的本地地址,不能使用通配地址”;--host 0.0.0.0 则会因用法错误退出。DSH 目前是开发者预览版,README 还用大写警告后续会发生不兼容变更。请在自己的安装中确认这两个值,不要信任任何页面,包括本文。

dsh --profile web --dump-config
ss -ltnp | grep 3080

--dump-config 会打印组合后的配置树,但不会启动 agent,因此可以显示经过所有补丁层后最终生效的主机地址和端口。ss 会显示当前实际处于监听状态的地址。正常情况下,输出行如下:

LISTEN 0  511  127.0.0.1:3080  0.0.0.0:*  users:(("node",pid=8123,fd=24))

只读取冒号前的地址,其他内容无需关注。127.0.0.1:3080 表示仅监听回环地址,这正是所需配置。其他地址表示 DSH 正在为该地址所属的网络提供服务,而该网络可能是公网。如果 ss 完全没有输出,说明 DSH 未运行;在它启动前,任何隧道都无法解决问题。在 VPS 上安装并运行 DeepSeek Harness介绍了如何完成这些步骤。

命令行标志只对当前运行有效。要永久保存某个值,应改用配置文件的 profile 补丁层。dsh --profile <name> 会在 $DSH_HOME/profiles/<name> 启动 profile;各层按以下顺序叠加:bundle 补丁、profile 自身的 cordis.patch.yml、主目录级别的 $DSH_HOME/cordis.patch.yml,以及任何 --patch 覆盖层。监听设置位于 @deepseek-ai/dsh-host-webserver plugin 下的 host 和 port 中。编辑前先使用 --dump-config 读取自己的组合配置树,因为 Web profile 首次启动时会根据随程序提供的模板自动写入配置,而预览版会持续更改该模板的结构。

启动输出行也是获取凭据的来源。DSH 会打印根 URL,并在 token 查询参数中附带进程令牌;只有 GET /?token=... 会接受该令牌,API 路径和 Authorization 标头都不会接受。该请求会设置一个带签名、仅限当前主机、HttpOnly、SameSite=Strict cookie,然后重定向到干净的 URL。令牌在每次重启时都会变化,且不会被存储;cookie 的有效期默认最长为 30 days。没有有效凭据的请求只会收到一个最小化的 401 响应。请复制完整的 URL,包括令牌,因为下面的每种方法最后都需要打开该 URL。

第二道关卡:/api 信任边界

让数据包到达 3080 端口只是问题的一半。DSH 还会执行第二项检查。这会导致一种容易混淆的故障:页面可以加载,布局也能显示,但随后所有功能都无法使用。

@deepseek-ai/dsh-client-connection 插件包含一个 trustedHosts 设置。其 README 规定:Host 标头必须是 loopback、监听器在任意端口上的自身绑定地址,或与 trustedHosts 条目匹配;浏览器发送 Origin 标头时,该标头必须指定相同的 authority。每个请求都会在身份验证前经过此边界检查。任何未通过检查的 /api 请求都会被拒绝。浏览器会这样报告拒绝:

transport failure for /api/host.describe: HTTP 403

因此,放在 DSH 前面的任何代理都必须声明浏览器地址栏中输入的名称。CLI 使用 --trusted-host <authority>,该参数可以重复指定:

dsh web --trusted-host dsh.example.com

此边界会匹配 authority,即主机加端口,并根据监听器使用的 scheme 规范化默认端口。不带端口的条目(例如 dsh.example.com)会匹配该名称的任意端口,因此也涵盖 dsh.example.com:8443。带端口的条目(例如 dsh.example.com:8443)只匹配指定的主机和端口,不匹配其他地址。loopback 无需添加条目,因此 localhost:3080 和 127.0.0.1:3080 都会通过检查。无法解释的 403 几乎总是因为 URL 地址栏中的名称与信任列表中的名称不同。401 是另一类问题:请求已通过边界检查,但浏览器还没有会话 Cookie。

此边界执行的是 origin 检查,不是身份验证。浏览器本身禁止网页设置 Host 标头,因此该检查确实可以阻止其他站点上的页面驱动您的 agent。非浏览器客户端可以自由写入该标头。请将 trustedHosts 视为代理兼容性设置,绝不要将其当作安全控制措施。

选项 1:SSH 本地转发

优先使用此方法,因为它完全不需要改动服务器。

ssh -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps

-L 会在笔记本电脑上打开一个监听器,并将每个连接转发到 VPS 上解析出的 127.0.0.1:3080。-N 表示“不运行远程命令”,因此会话只承载转发,不执行其他操作。将本地端写成 127.0.0.1:3080 而不是直接使用 3080 是有意为之:这样可将笔记本电脑一侧的监听器固定到 loopback,避免 SSH 客户端配置中的 GatewayPorts 设置将代理无意中重新发布到当前所在的网络。

现在,在笔记本电脑的浏览器中打开 DSH 输出的启动 URL,包括 token。由于转发监听在 127.0.0.1:3080 上,输出的 http://127.0.0.1:3080/?token=... 无需修改即可使用。在新浏览器会话中,仅使用 http://localhost:3080 不够:没有 cookie 时,DSH 会返回 401。首次访问后,cookie 会完成身份验证,但它仅对当前主机名有效。因此,请继续使用首次打开的名称,因为为 127.0.0.1 设置的 cookie 不会发送到 localhost。这里有两点可以直接生效。Host 头表示 loopback 权限来源,因此 /api 检查无需任何配置即可通过。浏览器还会将 http://127.0.0.1 和 http://localhost 等 loopback origin 视为安全上下文。这一点很重要,因为 DSH Web 应用启动时会调用 crypto.randomUUID(),而浏览器仅在 HTTPS 或 loopback origin 上提供该功能。

添加 -f,使 ssh 在转发建立后自动转入后台:

ssh -f -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps

需要明确的是:不会有新的监听器绑定到公网地址,也不会修改防火墙规则,因此这是可能增加的最小暴露面。全部访问控制都依赖 SSH 配置,所以仅使用密钥的 SSH,并禁用密码登录不是可选增强项,而是前提条件。代价是隧道只属于一个客户端会话。笔记本电脑进入休眠后隧道会断开;你需要手动重新启动它;而且手机无法使用该隧道。

选项 2:在 VPS 上使用 Tailscale serve

Tailscale 会在您自己的设备之间建立专用网络。在 VPS 上安装 Tailscale 后,该计算机会获得一个 100.x.y.z 地址。只有 tailnet 中的设备可以路由到此地址,具体哪些设备可以连接则由访问控制策略决定。共享该计算机后,外部用户的设备也会获得此访问权限。

加入该网络还不够,这也是许多用户遇到问题的地方。DSH 没有监听 100.x.y.z 地址,因为它监听的是回环地址。浏览器访问 http://100.x.y.z:3080 时会收到连接被拒绝,因为没有套接字绑定到该地址。--host 确实可以接受这个具体地址,但绑定到非回环地址时会提供普通 HTTP,并显示警告,从而重新引入选项 1 中的安全上下文问题。

tailscale serve 用于连接这两部分。它运行在 VPS 上,接收来自专用网络的流量,并将其代理到本地地址:

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
sudo tailscale serve --bg --https=443 localhost:3080
tailscale serve status

tailscale serve status 会以 https://<machine>.<tailnet>.ts.net/ 的形式输出 URL。使用该名称启动 DSH 并将其设为受信任名称,否则 /api 会返回 403,具体原因与上文相同:

dsh web --public-url https://your-vps.your-tailnet.ts.net/ --trusted-host your-vps.your-tailnet.ts.net

--public-url 只会改变 DSH 输出的内容,因此启动 URL 会使用 tailnet 名称,并可直接在手机上打开。它本身不会授予任何访问权限,因此仍需使用 --trusted-host。输出的 URL 包含有效凭据,因此只能将其粘贴到您自己的设备上。

这也会解决安全上下文问题。Tailscale 会使用为该名称签发的证书终止真正的 HTTPS,因此 crypto.randomUUID() 可用,UI 也能在手机浏览器中启动。没有任何内容会到达公网,因为 serve 只会在您的专用网络内部发布。

不要在这里替换为 tailscale funnel。它属于同一命令系列,但目标是公网;使用它会让一个具有 shell 访问权限的代理运行在任何人都能解析的主机名上。在执行其中任一命令前,请阅读了解 serve 如何将流量限制在您的 tailnet 内,而 funnel 会将其发布到公网。如需查看适用于手机的完整配置,请参阅从手机访问自行托管的代理。

这种方式需要依赖额外的网络组件。所有需要使用 UI 的设备都必须加入该网络,而且由您不负责运行的协调服务器决定哪些设备属于该网络。如果这不可接受,请参阅自行托管 Headscale 控制服务器,它可以在您拥有的硬件上使用相同的协议。

选项 3:经过身份验证的 TLS 反向代理

当浏览器无法加入私有网络时使用此方案,例如访问者使用的是您无法管理的计算机。现在您要将一个主机名发布到互联网,因此身份验证必须真实有效,并且必须由代理执行。DSH 的启动令牌是 URL 中的持有者凭据,所有持有该 URL 的人共享同一个令牌,因此不能算作这一层身份验证。

TLS 表示传输层安全,是 https:// 背后的加密机制。让 nginx 指向回环端口,并在前面添加密码验证。map 块应放在 http 上下文中,因此请在 /etc/nginx/conf.d/ 下为它创建单独的文件:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

然后配置站点本身:

server {
    listen 443 ssl;
    server_name dsh.example.com;

    ssl_certificate     /etc/letsencrypt/live/dsh.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/dsh.example.com/privkey.pem;

    auth_basic           "dsh";
    auth_basic_user_file /etc/nginx/dsh.htpasswd;

    location / {
        proxy_pass http://127.0.0.1:3080;
        proxy_http_version 1.1;
        proxy_set_header Host              $host;
        proxy_set_header Upgrade           $http_upgrade;
        proxy_set_header Connection        $connection_upgrade;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cookie_flags ~ secure;
        proxy_read_timeout 3600s;
        proxy_buffering    off;
    }
}

创建密码文件,测试配置,然后重新加载:

sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/dsh.htpasswd you
sudo nginx -t && sudo systemctl reload nginx

nginx -t 应输出 syntax is ok 和 test is successful。如果输出错误,nginx 会继续使用旧配置,因此当前服务尚未中断。然后使用受信任的公网名称启动 DSH,因为 proxy_set_header Host $host 会转发 dsh.example.com,否则安全校验会拒绝请求。--public-url 会让生成的启动 URL 使用该名称,因此您可以直接打开它:

dsh web --public-url https://dsh.example.com/ --trusted-host dsh.example.com

其中有 3 条指令不可缺少。Upgrade 和 Connection 请求头用于保持 UI 的长连接;缺少它们时,nginx 会关闭连接,界面会循环重连并显示过期状态。proxy_read_timeout 3600s 可防止 nginx 在 agent 运行时间超过默认的 60 秒时提前终止运行。proxy_cookie_flags ~ secure 会在 HTTPS 链路上为 DSH 的会话 Cookie 添加 Secure 标记。DSH 的公网部署指南要求代理执行此操作,因为纯 HTTP 后端发出的 Cookie 不会带有该标记。DSH 也可以通过 --tls-cert 和 --tls-key 自行提供 HTTPS,但它不会获取或续期证书,也不会添加密码,因此在此场景中不能替代代理。其余配置的原因请参阅 逐条解释 nginx 反向代理配置;代理本身的选择请参阅 nginx 与 Caddy 和 Traefik 的比较。

请明确基本身份验证能提供什么保护。它可以阻止随意扫描,远胜于开放端口。但它本质上仍是在 shell 前面设置一个共享密码,没有第二因素,也无法撤销某一个人的访问权限。任何获知该密码并持有启动 URL 或会话 Cookie 的人,都可以以 DSH 运行所使用的用户身份执行命令。如果需要让多人访问,请改为在前面配置真正的身份代理;在进一步扩大访问范围前,请阅读 在 VPS 上运行编码 agent 的安全规则。

绑定到 0.0.0.0 并开放端口会发生什么

显而易见的捷径是重新绑定并开放防火墙。DSH 会阻止通配地址这一部分。--host 拒绝 0.0.0.0,而 @deepseek-ai/dsh-host-webserver 插件在加载配置时会拒绝所有形式的未指定地址,包括 0.0.0.0、:: 以及 IPv4 映射地址。host 接受的是一个具体的 IPv4 或 IPv6 地址字面量,因此指定公网 IP 是 DSH 接受的配置。社区中有一些指南会修改已安装软件包,移除通配地址检查。这些指南附带警告,而且警告内容是准确的。将同一插件层用于相反方向更有价值:通过 支出上限和工具权限规则 限制代理可以执行的操作,而不是扩大其监听范围。

下面说明公开绑定实际会带来什么。端口 3080 会通过明文 HTTP 在公网 IP 地址上响应,DSH 也会打印警告。这里没有登录页面。启动令牌是唯一的保护措施,而且会以明文形式放在 URL 中传输;之后的会话 Cookie 也一样。/api 防护会检查 Host 标头,而任何非浏览器客户端都可以自行写入该标头;无论使用哪个端口,它仍会允许绑定地址。因此,即使在 trustedHosts 中列出你的地址,对持有 curl 的攻击者也没有任何作用。DSH 自己的部署指南说明,公开的 URL 和防护机制都不能保护监听端口本身。该端口后面运行的是一个以你的用户身份执行 shell 命令的代理,同时还能访问 $DSH_HOME 下的凭据;DSH 会在此保存配置文件和 API 密钥。令牌或 Cookie 只要被嗅探到一次,攻击者就能在你的服务器上远程执行代码,并产生由你承担的服务商费用。扫描器会持续扫描整个地址空间,因此未公开的 IP 不是有效的控制措施。代理可以读取的每个机密 都会随 shell 一起泄露。

上面的每种方法都是为了让你完全不必这样做。对于一个人在一台笔记本电脑上使用,SSH 转发是正确的默认方案。手机参与使用,或需要真正的证书时,改用 tailscale serve;只有在必须让你无法管理的浏览器访问 UI 时,才使用公网反向代理,并在其前面配置真正的身份验证。

FAQ

为什么从笔记本电脑无法打开 http://127.0.0.1:3080?

因为 127.0.0.1 表示“读取此地址的计算机”。DSH 在 VPS 上输出该地址,因此在 VPS 上是正确的。笔记本电脑读取相同字符串后,会访问自己的回环接口,而该接口没有进程监听。请在 VPS 上使用 ss -ltnp | grep 3080 检查服务器端。显示 127.0.0.1:3080 的行表示 DSH 正在运行,并且按预期绑定到回环地址。您需要的是隧道或代理,而不是更换绑定地址;之后请使用包含令牌的完整启动 URL。

可以使用 --host 0.0.0.0 运行 dsh web 吗?

不可以。2026 年 10 月 11 日核对的仓库 CLI 参考文档说明,--host 必须指定一个明确的本地地址,不能使用通配地址;--host 0.0.0.0 会因用法错误退出。Web 服务器插件在加载配置时也会拒绝相同的地址。类似公网 IP 的明确地址可以使用,但它提供的是纯 HTTP,因此用于保护 shell 的启动令牌会以明文在网络中传输。社区补丁会移除通配地址检查。如果应用此类补丁,DSH 不提供的防火墙配置和传输安全将由您负责。

为什么反向代理后的所有 /api 请求都返回 HTTP 403?

/api 信任边界会拒绝以下请求:其 Host 标头既不是回环地址、监听器的绑定地址,也未列在 trustedHosts 中。经过代理后,该标头包含您的公网名称,因此信任边界会拒绝请求,浏览器记录 transport failure for /api/host.describe: HTTP 403。请使用 --trusted-host <your name> 启动 DSH,并使其与地址栏中的名称一致。不带端口的条目会在任意端口上匹配该名称;带端口的条目则必须同时精确匹配主机和端口。如果请求返回 401,则表示信任边界已放行,但浏览器尚无会话。此时请使用带令牌的启动 URL。

为什么 DSH UI 可以加载,却无法通过纯 HTTP 完成启动?

Web 应用启动时会调用 crypto.randomUUID()。浏览器只会在安全上下文中提供该函数,例如 HTTPS,或 http://localhost 这样的回环来源。通过纯 HTTP 从裸 IP 地址提供服务时,该函数未定义,因此依赖它的调用会抛出错误,界面也无法填充。使用 tailscale serve 通过 HTTPS 提供服务,或使用 TLS 反向代理,可以解决此问题。通过 http://localhost 使用 SSH 转发访问也可以解决此问题。

单个开发者应使用哪种方法?

SSH 本地转发。它不会向服务器添加任何内容,不会修改防火墙规则,并且会复用您已经信任的 SSH 密钥。运行 ssh -f -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps,然后打开 DSH 输出的启动 URL http://127.0.0.1:3080/?token=...,并保留其中的令牌。希望在手机上使用 UI,或希望笔记本电脑休眠后仍能访问时,再改用 tailscale serve。