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 會回報格式為 http://127.0.0.1:3080/?token=... 的啟動 URL,而對讀取該位址的電腦而言,127.0.0.1 代表「讀取此位址的電腦」。筆記型電腦讀取這個位址時,會查看自身的 loopback 介面,但該介面上沒有任何程式正在監聽。如果問題在於位址本身令人困惑,為何 dsh 一開始會列印 http://127.0.0.1:3080 會更詳細地說明其中原因。不要變更 DSH 的繫結設定。請改為建立一條經過驗證的路徑,從筆記型電腦連至 VPS 的 loopback 位址。

loopback 預設值是正確的,本頁所有方法都會保留此設定。port 3080 後方執行的是可在伺服器上執行 shell 命令的 agent,而 Web UI 沒有提供登入頁面。DSH 現在會使用啟動 token 驗證瀏覽器:每次程序啟動時都會產生新的 token,列印出的 URL 會攜帶該 token,而首次造訪時會以此 token 換取 session cookie。這個 token 是放在 URL 中的單一 bearer credential,因此 loopback 仍是最重要的防護層:若要連到該 socket,必須先在該主機上取得 shell。

DSH 綁定的位址,以及如何檢查目前設定

截至 20 October 2026,DeepSeek Harness README 表示 npx @deepseek-ai/dsh web「預設會在 http://127.0.0.1:3080 啟動 Web UI」。同一個 repository 中的 CLI reference 表示,預設埠仍為 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 尚未執行;在 DSH 啟動前,任何通道都無法解決問題。相關安裝與啟動方式請參閱 在 VPS 上安裝及執行 DeepSeek Harness。

旗標只會套用於單次執行。若要讓設定持續生效,請改為編輯 profile patch layer。dsh --profile <name> 會在 $DSH_HOME/profiles/<name> 啟動 profile,而各層會依下列順序疊加: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 首次啟動時會依照隨附的 template 自動建立自身內容,而預覽版仍持續變更該 template 的結構。

啟動輸出行也會顯示認證憑證的來源。DSH 會在 token 查詢中輸出帶有 process token 的根 URL,只有 GET /?token=... 會接受該 token:不能使用 API path,也不能使用 Authorization header。該請求會設定經簽署、僅限該主機、HttpOnly、SameSite=Strict cookie,然後重新導向至乾淨的 URL。每次啟動程序都會產生新的 token,而 cookie 預設會在 30 days 後到期;此值由 cookieMaxAgeDays 設定。沒有有效認證的請求會收到 401。請完整複製輸出的 URL,包括 token,因為以下每種方法最後都需要開啟該 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 前方的任何 proxy 都必須宣告瀏覽器輸入的名稱。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 則是另一個問題:請求已通過這道邊界,但瀏覽器尚未取得 session cookie。

這道邊界是 origin 檢查,不是驗證機制。瀏覽器本身禁止頁面設定 Host 標頭,因此這項檢查確實能阻止其他網站上的頁面操作你的 agent。任何非瀏覽器用戶端都能自由寫入該標頭。請將 trustedHosts 視為 proxy 的相容性設定,絕不要將其視為安全控制。

選項 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 靜默重新發布到目前所在的網路。

現在,請在筆電的瀏覽器中開啟 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 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是必要條件,而不是可有可無的額外措施。代價是通道只屬於單一用戶端工作階段。筆電進入睡眠時通道會中斷,必須手動重新啟動,而且無法在手機上提供存取。

VPS 上的 Tailscale serve

Tailscale 會在你的裝置之間建立私有網路。將它安裝在 VPS 上後,該機器會取得 100.x.y.z 位址,只有 tailnet 中的裝置能透過路由連線到此位址;哪些裝置可以連線,則由你的存取控制政策決定。共用該機器後,外部使用者的裝置也會取得這項存取權。

加入該網路還不夠,許多人就是在這裡遇到問題。DSH 沒有監聽 100.x.y.z 位址,因為它正在監聽 loopback。瀏覽器連線到 http://100.x.y.z:3080 時會收到 connection refused,因為沒有 socket 綁定到該位址。--host 確實會接受這個具體位址,但非 loopback 綁定會提供未加密的 HTTP,除非你設定 TLS;DSH 也不會顯示警告,因此 Option 1 的 secure context 問題會再次出現。

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 並將其設為 trusted,否則 /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 含有有效憑證,因此只能貼到你自己的裝置上。

這也能解決 secure context 問題。Tailscale 會使用針對該名稱簽發的憑證終止真正的 HTTPS,因此 crypto.randomUUID() 可用,UI 也能在手機瀏覽器中啟動。因為 serve 只會在你的私有網路內發布,所以不會有任何內容到達公用網際網路。

此處不要改用 tailscale funnel。它是相同指令系列中面向公用網際網路的功能,會讓具備 shell 存取權的 agent 出現在任何人都能解析的主機名稱上。執行其中任一指令前,請先閱讀 serve 如何讓流量留在 tailnet 內,而 funnel 如何發布流量。如需這整套設定的手機使用方式,請參閱 從手機連線到自行託管的 agent。

這種方式的取捨是增加一項相依性。所有需要使用 UI 的裝置都必須加入該網路,而且由你未自行執行的協調伺服器決定哪些裝置屬於該網路。如果這點無法接受,自行託管的 Headscale 控制伺服器 可在你擁有的硬體上使用相同通訊協定。

選項 3:具備驗證功能的 TLS 反向代理

當瀏覽器無法加入私有網路時使用此方式,例如瀏覽器所在的機器不是由你管理。現在你會將主機名稱公開到網際網路,因此驗證機制必須是真正的驗證,且必須由代理伺服器提供。DSH 的 launch token 是 URL 中的一組 bearer credential,所有持有該 URL 的人都能使用,因此不能算作這一層防護。

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_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,否則 fence 會拒絕該請求。--public-url 會讓輸出的 launch URL 使用該名稱,因此你可以直接開啟:

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

其中有 3 個指令不可省略。Upgrade 和 Connection 標頭可讓 UI 的長時間連線保持開啟。若缺少這些標頭,nginx 會關閉連線,導致介面在顯示過期狀態的同時不斷重新連線。proxy_read_timeout 3600s 可避免 nginx 中斷執行時間超過預設 60 秒的 agent 工作。proxy_cookie_flags ~ secure 會在 HTTPS 連線上為 DSH 的工作階段 cookie Secure 加上標記。DSH 的公開部署指南要求代理伺服器這樣設定,因為純 HTTP 後端發出的 cookie 不會包含此標記。DSH 也能透過 --tls-cert 和 --tls-key 自行提供 HTTPS,但它不會取得或續期憑證,也不會加入密碼驗證,因此在此情境下不能取代代理伺服器。其餘設定的理由請參閱 逐一說明 nginx 反向代理設定中的指令;代理伺服器的選擇則請參閱 比較 nginx、Caddy 與 Traefik。

請清楚了解基本驗證能提供什麼保護。它能阻擋隨機掃描,安全性也遠高於直接公開連接埠。但它本質上仍是在 shell 前方共用 1 組密碼,沒有第二因素,也無法撤銷個別使用者的權限。任何取得該密碼,並持有 launch URL 或工作階段 cookie 的人,都能以 DSH 所使用的使用者身分執行命令。如果有多人需要存取,請改在前方加入真正的身分識別代理伺服器;在進一步放寬存取範圍前,請先閱讀 在 VPS 上執行 coding 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 行與瀏覽器交接通知。沒有登入頁面。啟動 token 是唯一的鎖,而且會以明文形式透過 URL 在網際網路上傳送;之後的工作階段 cookie 也是如此。/api 防護機制會檢查 Host 標頭,而任何非瀏覽器用戶端都能自行寫入該標頭;此外,無論使用哪個連接埠,它都會允許綁定位址,因此將你的位址列在 trustedHosts 中,對持有 curl 的攻擊者不會造成任何改變。DSH 自己的部署指南指出,廣告中的 URL 與防護機制都不會保護監聽中的連接埠本身。該連接埠後方是以你的使用者身分執行 shell 命令的代理程式,並可使用 $DSH_HOME 下的認證資料;DSH 會在此處保存設定檔與 API 金鑰。只要有一組 token 或 cookie 被竊聽,攻擊者就能在你的伺服器上遠端執行程式碼,相關費用也會記在你的 provider 帳單上。掃描器會持續掃描整個位址空間,因此未列出的 IP 不是控制措施。代理程式能讀取的每一項 secret 都會隨 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,而不是改用其他 bind address,接著使用包含 token 的完整啟動 URL。

我可以使用 --host 0.0.0.0 執行 dsh web 嗎?

不可以。Repository CLI reference 於 20 October 2026 查核後指出,--host 必須指定一個具體的本機位址,不能使用 wildcard;--host 0.0.0.0 則會因 usage error 而結束。Webserver plugin 載入設定時也會拒絕相同位址。使用 public IP 等具體位址可以通過,但服務會提供 plain HTTP,因此保護 shell 的 launch token 會以明文通過網路。社群修補程式會移除 wildcard 檢查。若套用這類修補程式,防火牆與 DSH 未提供的傳輸安全性都必須由你負責。

為什麼所有 /api 呼叫在 reverse proxy 後方都回傳 HTTP 403?

/api trust fence 會拒絕任何 Host header 不是 loopback address、listener 的 bind address,或未列在 trustedHosts 中的請求。在 proxy 後方,該 header 會帶有你的 public name,因此 trust fence 會拒絕請求,瀏覽器則記錄 transport failure for /api/host.describe: HTTP 403。請使用 --trusted-host <your name> 啟動 DSH,並讓它符合 URL 列中的名稱。不含 port 的項目會在任何 port 上符合該名稱;包含 port 的項目則必須完全符合 host 與 port。若呼叫改回傳 401,表示 trust fence 已放行,而瀏覽器尚未建立 session;請使用包含 token 的已輸出 launch URL。

為什麼 DSH UI 可以載入,卻在 plain HTTP 上永遠無法完成啟動?

Web app 啟動時會呼叫 crypto.randomUUID()。瀏覽器只會在 secure context 中提供此功能,例如 HTTPS,或 http://localhost 這類 loopback origin。若從 bare IP address 以 plain HTTP 提供服務,該功能會是 undefined,因此依賴它的呼叫會擲出錯誤,介面也永遠不會填入內容。使用 tailscale serve 透過 HTTPS 提供服務,或使用 TLS reverse proxy,即可排除問題;透過 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,然後開啟 DSH 輸出的 launch URL http://127.0.0.1:3080/?token=...,並保留其中的 token。當你希望在手機上使用 UI,或需要在筆記型電腦進入睡眠後仍維持存取時,再改用 tailscale serve。