VPSのDSH Web UIをノートPCで開く方法
DSH Web UIは127.0.0.1:3080にバインドされるため、VPSが表示したURLはノートPCでは開けません。安全に接続する3つの方法と避けるべき方法を説明します。
ノート PC から DSH Web UI を開けない理由
DSH Web UI は loopback インターフェースにバインドされるため、端末に表示された URL は、その URL を表示したマシンでしか機能しません。npx @deepseek-ai/dsh web は http://127.0.0.1:3080 を示し、127.0.0.1 は、そのアドレスを読み取るコンピューターにとって「このアドレスを読み取っているコンピューター」を意味します。ノート PC はそのアドレスを読み取り、自身の loopback インターフェースを確認しますが、そこでは何も待ち受けていません。アドレス自体が分かりにくい場合は、dsh がそもそも http://127.0.0.1:3080 を表示する理由で詳しく説明しています。DSH のバインド先は変更しないでください。ノート PC から VPS の loopback アドレスへ、認証済みの経路を構築します。
loopback をデフォルトにする設定は正しく、このページのすべての方法で維持します。ポート 3080 の背後で動作するのは、サーバー上で shell コマンドを実行する agent です。Web UI にはログインページがありません。ここで DSH が利用できる唯一のアクセス制御は loopback です。ソケットに接続するには、すでにそのサーバー上で shell を利用できなければなりません。
DSH がバインドする先と確認方法
2026年8月17日時点で、DeepSeek Harness の README には、npx @deepseek-ai/dsh web が「デフォルトで http://127.0.0.1:3080 で提供される Web UI を起動する」と記載されています。同じリポジトリの CLI リファレンスでは、--port <num> はデフォルト値が 3080 の上書き指定、--host <addr> は「0.0.0.0 を意図的に拒否する」上書き指定として説明されています。DSH は開発者向けプレビューであり、README では互換性を損なう変更が発生すると大文字で警告しています。どのページも、この記事を含めてそのまま信頼せず、自分のインストール環境で両方の値を確認してください。
dsh --profile web --dump-config
ss -ltnp | grep 3080--dump-config はエージェントを起動せずに合成後の設定ツリーを表示するため、すべてのパッチ層を通過したホストとポートを確認できます。ss は現在実際に待ち受けているものを表示します。正常な行は次のようになります。
LISTEN 0 511 127.0.0.1:3080 0.0.0.0:* users:(("node",pid=8123,fd=24))コロンより前のアドレスだけを確認してください。127.0.0.1:3080 はループバックのみであり、望ましい設定です。0.0.0.0:3080 は、そのホスト上のすべてのインターフェース、つまりパブリックインターフェースも意味します。ss が何も表示しない場合、DSH は実行されていません。DSH が起動するまで、トンネルを設定しても接続できません。そこまでの手順は、VPS に DeepSeek Harness をインストールして実行するで説明しています。
フラグは1回の実行にだけ適用されます。値を永続化するには、代わりにプロファイルのパッチ層を編集してください。dsh --profile <name> は $DSH_HOME/profiles/<name> のプロファイルを起動します。層は、bundle のパッチ、プロファイル自身の cordis.patch.yml、home レベルの $DSH_HOME/cordis.patch.yml、最後に任意の --patch オーバーレイの順で積み重なります。待ち受け設定は @deepseek-ai/dsh-host-webserver プラグインの host と port にあります。編集前に --dump-config で自分の合成後のツリーを確認してください。Web プロファイルは初回起動時に、配布済みテンプレートから自身を生成します。また、プレビュー版ではそのテンプレートの構造が変更され続けています。
第 2 のゲート: /api の信頼境界
ポート 3080 までパケットを到達させるだけでは、問題の半分しか解決していません。DSH にはもう 1 つのチェックがあり、ページとレイアウトは表示されるのに、その後何も動作しないという分かりにくい障害を引き起こします。
@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 ヘッダーを単純な文字列として比較します。そのため、上記には 2 通りの表記が示されています。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 は「リモートコマンドを実行しない」という意味です。そのため、セッションにはフォワードだけが含まれます。ローカル側を単なる 3080 ではなく 127.0.0.1:3080 と記述するのは意図的です。これにより、ラップトップ側のリスナーを loopback に固定できます。SSH クライアント設定の GatewayPorts によって、agent が接続中のネットワークへ意図せず再公開されることもありません。
次に、ブラウザーで http://localhost:3080 を開きます。ここでは、2 つの点が設定なしで機能します。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 は、あると便利な追加策ではなく前提条件です。一方、トンネルは 1 つのクライアントセッションに属します。ラップトップがスリープすると切断され、手動で再起動する必要があります。また、スマートフォンからは利用できません。
VPS 上での Tailscale serve
Tailscale は、自分のデバイス間にプライベートネットワークを構築します。VPS にインストールすると、そのマシンに 100.x.y.z アドレスが割り当てられます。このアドレスへルーティングできるのは、自分のデバイスだけです。
そのネットワークに参加させるだけでは不十分です。ここで詰まる人が多くいます。DSH は loopback で待ち受けているため、100.x.y.z アドレスでは待ち受けていません。ブラウザーで http://100.x.y.z:3080 を開いても、そのアドレスにソケットがバインドされていないため、接続拒否になります。
tailscale serve が、この2つを接続する役割を担います。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 control server を使用できます。自分が所有するハードウェア上で、同じプロトコルを処理します。
認証付き TLS リバースプロキシ
ブラウザーからプライベートネットワークに参加できない場合に使用します。たとえば、自分で管理していないマシンを操作する場合です。ここではホスト名をインターネットに公開するため、認証は実際に機能し、プロキシから提供される必要があります。DSH は認証機能を提供しません。
TLS は transport layer security を意味し、https:// の背後にある暗号化技術です。nginx を loopback ポートに向け、その前段にパスワード認証を配置します。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_read_timeout 3600s;
proxy_buffering off;
}
}パスワードファイルを作成し、設定をテストしてから reload します。
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ここにある 2 つのディレクティブは不可欠です。Upgrade ヘッダーと Connection ヘッダーにより、UI の長時間接続を維持できます。これらがないと nginx が接続を閉じるため、古い状態を表示したままインターフェースがループ状に再接続します。proxy_read_timeout 3600s により、デフォルトの 60 秒を超えて処理する agent run が nginx によって切断されるのを防ぎます。その他の設定理由については、nginx のリバースプロキシ設定をディレクティブごとに解説した説明を参照してください。プロキシ自体の選択については、nginx、Caddy、Traefik の比較を参照してください。
Basic 認証で得られる効果を明確に理解してください。無差別なスキャナーを防止でき、ポートを開放したままにするよりはるかに安全です。一方で、shell の前段に 1 つの共有パスワードを置くだけであり、2 要素認証も、特定のユーザーだけを無効化する方法もありません。パスワードを知った人は誰でも、DSH が実行されるユーザーとしてコマンドを実行できます。複数の人がアクセスする必要がある場合は、代わりに実際の identity proxy を前段に配置し、公開範囲をさらに広げる前に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 だけを受け付けると説明されています。そのため、サポートされる値は 1 つで、もう 1 つは CLI に拒否されます。チェックを無効化する Community plugins もあります。警告付きで提供されており、その警告は正しい内容です。同じプラグイン層は、リスニング範囲を広げるよりも、支出上限とツール権限ルール によってエージェントの実行可能な操作を制限する方向で使う方がはるかに有用です。
実際に強制すると、次の状態になります。ポート 3080 はパブリック IP アドレスで応答します。ログイン画面はありません。/api の防御は Host ヘッダーを検査しますが、このヘッダーはブラウザー以外のクライアントが自分で設定できます。そのため、trustedHosts にアドレスを登録しても、curl を持つ攻撃者には何の効果もありません。公開されるのは、あなたのユーザー権限で shell コマンドを実行するエージェントです。さらに、DSH がプロファイルと API keys を保存する $DSH_HOME 内の認証情報も利用できます。これは、プロバイダーの請求先まで含めて、サーバー上でリモートコード実行を許すことを意味します。スキャナーはアドレス空間全体を継続的に走査するため、IP を一覧に載せていないことは対策になりません。エージェントが読み取れるすべての Secret は shell とともに外部へ送信されます。
上記の各方法は、これを実行せずに済むように用意されています。1 人が 1 台の laptop から使う場合は、SSH forward が適切な既定値です。phone を使う場合、または正式な証明書が必要な場合は tailscale serve に移行します。管理していない browser から UI にアクセスする必要がある場合に限り、公開 reverse proxy を使用し、その前段に実際の認証を配置してください。
FAQ
http://127.0.0.1:3080 がノート PC から開けないのはなぜですか?
127.0.0.1 は「このアドレスを読み取っているマシン」を意味するためです。DSH は VPS 上でこのアドレスを表示するため、その環境では正しい値です。ノート PC は同じ文字列を読み取り、自身のループバックインターフェースを参照しますが、そこでは何も待ち受けていません。VPS 上で ss -ltnp | grep 3080 を実行し、サーバー側を確認してください。127.0.0.1:3080 を示す行があれば、DSH は意図どおりループバックで実行され、待ち受けています。必要なのは bind アドレスの変更ではなく、トンネルまたはプロキシです。
dsh web を --host 0.0.0.0 で実行できますか?
いいえ。2026 年 8 月 17 日に確認したリポジトリの CLI リファレンスでは、--host <addr> は 0.0.0.0 を意図的に拒否するオーバーライドとして記載されています。DSH は shell command を実行し、login page を提供しないためです。すべてのインターフェースで待ち受けると、認証されていない shell が公開 IP アドレス上に公開されます。コミュニティの patch ではこのチェックを削除できます。適用する場合、firewall と、DSH が提供しない認証は利用者の責任になります。
reverse proxy の背後で /api のすべての呼び出しが HTTP 403 を返すのはなぜですか?
/api の trust fence は、Host header が loopback address でも trustedHosts に登録された値でもない request を拒否します。proxy の背後では、その header に公開名が入るため、fence が request を拒否し、browser に transport failure for /api/host.describe: HTTP 403 が記録されます。--trusted-host <your name> を指定して DSH を起動し、表記を完全に一致させてください。URL に port が含まれる場合は、port も含める必要があります。比較は文字列の完全一致だからです。
DSH UI は読み込めるのに、plain HTTP では起動が完了しないのはなぜですか?
web app は起動時に crypto.randomUUID() を呼び出します。browser はこの機能を secure context、つまり HTTPS または http://localhost のような loopback origin でのみ公開します。bare IP address から plain HTTP で提供すると、この機能は undefined になります。そのため、この機能に依存する呼び出しが例外を発生させ、interface が表示されません。tailscale serve を使う HTTPS または TLS reverse proxy 経由で提供すれば解決します。http://localhost への SSH forward 経由でアクセスしても解決します。
1 人の開発者にはどの方法が適していますか?
SSH local forward です。サーバーへの追加導入は不要で、firewall rule も変更せず、すでに信頼している SSH key をそのまま使用できます。ssh -f -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps を実行してから、http://localhost:3080 を開いてください。スマートフォンで UI を利用したい場合や、ノート PC がスリープしてもアクセスを維持したい場合は、tailscale serve に移行してください。