VPS 上如何開啟 DSH Web UI
DSH Web UI 綁定 127.0.0.1:3080,VPS 顯示的 URL 無法直接在筆記型電腦開啟。本文說明 3 種安全連線方式,並指出應避免的方法。
為什麼從筆記型電腦無法開啟 DSH Web UI
DSH Web UI 綁定回送介面,因此終端機列印的 URL 只能在列印該 URL 的機器上運作。npx @deepseek-ai/dsh web 回報 http://127.0.0.1:3080,而對讀取該位址的電腦而言,127.0.0.1 代表「讀取這個位址的電腦」。筆記型電腦會讀取這個位址,查看自己的回送介面,但該介面沒有任何程式在監聽。如果問題出在位址本身,為什麼 dsh 會列印 http://127.0.0.1:3080 會更詳細地說明原因。不要變更 DSH 的綁定介面。請從筆記型電腦建立通往 VPS 回送位址的已驗證連線。
回送介面的預設設定是正確的,本頁的所有方法都會保留這項設定。3080 埠後方執行的是可在伺服器上執行 shell 命令的代理程式,而 Web UI 沒有登入頁面。回送介面是 DSH 在此處唯一的存取控制:若要連線至該 socket,您必須先在該主機上取得 shell。
DSH 綁定的位址,以及如何檢查自己的設定
截至 17 August 2026,DeepSeek Harness README 表示 npx @deepseek-ai/dsh web「預設會啟動 Web UI,並在 http://127.0.0.1:3080 提供服務」。同一個 repository 中的 CLI reference 將 --port <num> 記載為可覆寫 3080 預設值的選項,並將 --host <addr> 記載為會「刻意拒絕 0.0.0.0」的覆寫選項。DSH 目前是開發者預覽版,README 也以大寫警告未來會有不相容變更。請在自己的安裝環境確認這兩個值,不要信任任何頁面,包括本頁。
dsh --profile web --dump-config
ss -ltnp | grep 3080--dump-config 會列印組合後的設定樹,但不會啟動 agent,因此可顯示所有 patch layer 套用後實際生效的主機與埠。ss 會顯示目前實際正在監聽的項目。正常的輸出行如下:
LISTEN 0 511 127.0.0.1:3080 0.0.0.0:* users:(("node",pid=8123,fd=24))只讀取冒號前的位址,其他內容不必理會。127.0.0.1:3080 僅繫結至 loopback,這才是預期設定。0.0.0.0:3080 表示該主機上的所有介面都會監聽,包括公開介面。如果 ss 完全沒有列印任何行,表示 DSH 尚未執行;在它啟動前,任何 tunnel 都無法解決問題。如何執行到這一步,請參閱在 VPS 上安裝並執行 DeepSeek Harness。
旗標只適用於單次執行。若要讓設定持續生效,請改為編輯 profile patch layer。dsh --profile <name> 會以 $DSH_HOME/profiles/<name> 啟動 profile;各 layer 的套用順序如下:bundle patches、profile 自身的 cordis.patch.yml、home-level 的 $DSH_HOME/cordis.patch.yml,以及任何 --patch overlay。監聽設定位於 @deepseek-ai/dsh-host-webserver plugin 下的 host 與 port。編輯前,請先使用 --dump-config 讀取自己的組合設定樹,因為 web profile 首次啟動時會依 shipped template 自動建立自身設定,而預覽版仍持續變更該 template 的結構。
第二道閘門:/api 信任邊界
讓封包抵達 3080 埠,只完成了一半。DSH 還有第二項檢查。這項檢查會造成一個容易混淆的失敗狀況:頁面可以載入、版面也會顯示,但之後所有功能都無法使用。
@deepseek-ai/dsh-client-connection 外掛包含 trustedHosts 設定,文件將其說明為「此部署在 loopback 之外提供服務的權限來源:精確的 host:port,或可比對任何埠的省略埠號 host」。若 Host 標頭既不是 loopback,也未列在該設定中,邊界就會拒絕該 /api 請求。瀏覽器會以以下方式回報拒絕:
transport failure for /api/host.describe: HTTP 403因此,放在 DSH 前方的任何代理伺服器,都必須宣告瀏覽器輸入的名稱。CLI 接受 --trusted-host <authority>,而且可以重複指定:
dsh web --trusted-host dsh.example.com --trusted-host dsh.example.com:8443邊界會將 Host 標頭當成純字串比較,因此上方才會出現兩種寫法。對它而言,dsh.example.com 與 dsh.example.com:8443 是兩個不同的權限來源,localhost:3080 與 127.0.0.1:3080 也是如此。無法解釋的 403,幾乎總是 URL 列與信任清單之間的拼寫不同所造成。
這項邊界是來源檢查,不是驗證機制。瀏覽器本身禁止網頁設定 Host 標頭,因此這項檢查確實能阻止其他網站上的頁面操控代理程式。任何非瀏覽器用戶端都能自由寫入該標頭。請將 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 設定在不知情的情況下,將 agent 重新發布到目前所在的網路。
現在在瀏覽器中開啟 http://localhost:3080。這裡有兩項功能不需額外設定即可運作。Host 標頭代表 loopback authority,因此 /api 防護可直接通過。瀏覽器也會將 http://localhost 視為 secure context;這點很重要,因為 DSH web app 啟動時會呼叫 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 上後,該機器會取得只有你的裝置能夠路由到的 100.x.y.z 位址。
加入該網路還不夠,問題通常就出在這裡。DSH 沒有監聽 100.x.y.z 位址,因為它正在監聽 loopback。瀏覽器連線到 http://100.x.y.z:3080 時會遭到拒絕,因為沒有 socket 綁定到該位址。
tailscale serve 負責連接這兩者。它在 VPS 上執行,接受來自私有網路的流量,再將流量代理到本機位址:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
sudo tailscale serve --bg --https=443 localhost:3080
tailscale serve statustailscale serve status 會以 https://<machine>.<tailnet>.ts.net/ 的形式輸出 URL。請使用該名稱啟動 DSH 並設為信任來源,否則 /api 會回傳 403,情況與上述相同:
dsh web --trusted-host your-vps.your-tailnet.ts.net這也能解決安全內容環境問題。Tailscale 會使用針對該名稱簽發的憑證終止真正的 HTTPS,因此 crypto.randomUUID() 可用,UI 也能在手機瀏覽器中啟動。因為 serve 只會在你的私有網路內發布服務,所以不會有任何流量到達公用網際網路。
此處不要改用 tailscale funnel。它屬於相同的命令系列,但目標是公用網際網路;使用後,任何人都能解析該主機名稱,並在其上找到具備 shell 存取權的 agent。輸入任一命令前,請先閱讀 了解 serve 如何讓流量留在 tailnet 內,而 funnel 如何發布流量。如需這套完整設定的手機版本,請參閱 從手機連線到自行託管的 agent。
代價是增加一項相依性。所有需要 UI 的裝置都必須加入該網路,而且由你不負責執行的協調伺服器決定哪些裝置可以加入。如果這點無法接受,自行託管的 Headscale 控制伺服器 可在你擁有的硬體上使用相同的協定。
選項 3:具備驗證功能的 TLS 反向代理
當瀏覽器無法加入私有網路時使用此方式,例如該機器不由您管理。現在您要將主機名稱公開到網際網路,因此驗證機制必須確實運作,且必須由代理伺服器提供。DSH 不會提供任何驗證機制。
TLS 是傳輸層安全性,負責加密 https:// 背後的連線。讓 nginx 指向 loopback 埠,並在前方加上密碼驗證。map 區塊必須放在 http context 中,因此請在 /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_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 nginxnginx -t 應輸出 syntax is ok 和 test is successful。如果改為輸出錯誤訊息,nginx 會繼續提供舊設定,因此目前尚未造成服務中斷。接著以信任公開名稱的方式啟動 DSH,因為 proxy_set_header Host $host 會轉送 dsh.example.com,否則防護機制會拒絕該連線:
dsh web --trusted-host dsh.example.com其中有兩個指示詞不可省略。Upgrade 和 Connection 標頭會維持 UI 所需的長時間連線;若缺少這些標頭,nginx 會關閉連線,導致介面在顯示過時狀態時持續重新連線。proxy_read_timeout 3600s 可避免 nginx 中斷執行時間超過預設 60 秒的 agent 工作。其餘設定的原因請參閱 逐一說明 nginx 反向代理設定中的指示詞;代理伺服器的選擇則請參閱 nginx、Caddy 與 Traefik 的比較。
請清楚了解基本驗證能提供什麼。它可以阻擋隨機掃描,且遠比開放連接埠安全。但它也只是在 shell 前方放置一組共用密碼,沒有第二因素驗證,也無法撤銷單一使用者的存取權。任何取得該密碼的人,都能以 DSH 執行時所使用的使用者身分執行命令。如果有多人需要存取,請改在前方部署真正的身分識別代理,並先閱讀 在 VPS 上執行 coding agent 的安全規則,再進一步擴大存取範圍。
如果繫結至 0.0.0.0 並開放連接埠,會發生什麼事
最直接的做法是重新繫結並開放防火牆。DSH 會阻擋前半段。--host <addr> 會刻意拒絕 0.0.0.0,而 @deepseek-ai/dsh-host-webserver 外掛文件說明 host 只接受 127.0.0.1 或 0.0.0.0,因此只有一個受支援的值,另一個則會被 CLI 拒絕。社群中有些外掛會修補這項檢查。這些外掛附帶警告,而且警告內容是正確的。將同一層外掛用於相反方向更有價值:透過 支出上限與工具權限規則 限制代理程式可執行的操作,而不是放寬其監聽範圍。
實際強制啟用後,結果如下。連接埠 3080 會在公開 IP 位址上回應。系統沒有登入頁面。/api 防護機制會檢查 Host 標頭;任何非瀏覽器用戶端都能自行寫入這個標頭,因此將位址列入 trustedHosts 對持有 curl 的攻擊者毫無作用。你公開的是一個能以你的使用者身分執行 shell 命令的代理程式,以及 $DSH_HOME 中的憑證;DSH 會在該處儲存設定檔與 API 金鑰。這等同於在你的伺服器上提供遠端程式碼執行能力,並由你的服務供應商帳單承擔費用。掃描器會持續掃描整個位址空間,因此未列出的 IP 並不是防護措施。代理程式能讀取的每個秘密 都可能隨 shell 一起外洩。
上述每種方法的目的,都是讓你不必這樣做。對於一個人在一台筆電上使用,SSH 轉送是正確的預設方式。當涉及手機,或你需要正式憑證時,改用 tailscale serve;只有在未由你管理的瀏覽器必須存取 UI 時,才使用公開反向代理,並在前方配置真正的驗證機制。
FAQ
為什麼我在筆電上無法開啟 http://127.0.0.1:3080?
因為 127.0.0.1 代表「讀取這個位址的機器」。DSH 會在 VPS 上輸出這個位址,而該位址在 VPS 上是正確的。你的筆電讀取相同字串後,會查看自己的 loopback 介面,但該介面上沒有任何服務在監聽。請在 VPS 上使用 ss -ltnp | grep 3080 檢查伺服器端。若輸出列顯示 127.0.0.1:3080,表示 DSH 正在執行,且刻意繫結至 loopback。你需要的是 tunnel 或 proxy,而不是更換繫結位址。
我可以使用 --host 0.0.0.0 執行 dsh web 嗎?
不行。2026 年 8 月 17 日查閱的 repository CLI reference 將 --host <addr> 定義為會刻意拒絕 0.0.0.0 的覆寫選項。原因是 DSH 會執行 shell 命令,且沒有提供登入頁面。因此,繫結至所有介面會在你的公開 IP 位址上發布未經驗證的 shell。社群修補程式會移除這項檢查。若套用修補程式,防火牆設定以及 DSH 未提供的驗證機制都由你負責。
為什麼透過反向代理時,所有 /api 呼叫都回傳 HTTP 403?
/api trust fence 會拒絕任何 Host 標頭既不是 loopback 位址,也未列在 trustedHosts 中的請求。透過 proxy 時,該標頭會帶有你的公開名稱,因此 trust fence 會拒絕請求,而瀏覽器會記錄 transport failure for /api/host.describe: HTTP 403。請使用 --trusted-host <your name> 啟動 DSH,並完全比對拼寫。若 URL 包含連接埠,也必須包含該連接埠,因為比對採用字串逐字比對。
為什麼 DSH UI 載入後,使用純 HTTP 時卻一直無法完成啟動?
Web 應用程式啟動時會呼叫 crypto.randomUUID()。瀏覽器只會在安全內容環境中提供此功能,例如 HTTPS,或 http://localhost 這類 loopback origin。若透過純 HTTP 從一般 IP 位址提供服務,該功能會是 undefined,因此依賴它的呼叫會擲出錯誤,介面也不會填入內容。使用 tailscale serve 或 TLS reverse proxy 透過 HTTPS 提供服務即可排除此問題。透過 http://localhost 上的 SSH forward 存取也可以解決問題。
單一開發人員應使用哪種方法?
使用 SSH local forward。它不會對伺服器新增任何元件,也不會變更防火牆規則,並且能重複使用你已信任的 SSH key。執行 ssh -f -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps,然後開啟 http://localhost:3080。若你希望在手機上使用 UI,或希望存取方式不受筆電進入睡眠狀態影響,請改用 tailscale serve。