はじめに
AI エージェント Muse が作業しているクラウド上の VM には、外部から入ってくるネットワーク経路がありません。公開 IP もなければ、ポート開放の手段もない。それでも「自宅 PC からこの VM に SSH で入って、ファイルをやり取りしたり、VM 内のサービスをブラウザで見たりしたい」という要件がありました。
解決策は古典的で、VM の内側から外へリバーストンネルを張り、その先に sshd をぶら下げる方式です。本記事はその構築記録で、途中のつまずき(apt が使えない、sshd が起動を拒否する、鍵が無視される、転送設定の矛盾)をエラー文つきで残します。
なお、接続先アドレスや鍵情報は運用上の秘匿情報なので、本記事には書きません。アドレスは「60分ごとに変わる使い捨て」という前提で読んでください。
環境と構成
| 項目 | 内容 |
|---|---|
| VM | Ubuntu 24.04 / 2 vCPU / インバウンド経路なし |
| sshd | OpenSSH 9.6p1(.deb を展開して使用) |
| sshd 待ち受け | 127.0.0.1:2222 のみ、公開鍵認証のみ、パスワード認証オフ |
| 公開 | Pinggy 無料トンネル(リバーストンネル)でトンネル先を 127.0.0.1:2222 に転送 |
| 常駐化 | 自作 supervisor スクリプトが sshd とトンネルを監視し、落ちたら再起動 |
| クライアント | 自宅 PC(Windows)の OpenSSH クライアント |
ポイントは、sshd はループバックでしか待ち受けていないことです。外に出ているのは VM 側から張ったトンネルだけで、到達できるのは接続用鍵を持つクライアントだけ、という形にしています。
つまずき1:apt がプロキシ経由で使えない
openssh-server を普通に apt で入れようとしたら、プロキシの応答エラーで落ちました。
E: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/noble/InRelease Invalid response from proxy: HTTP/1.0 400 Bad Request [IP: ::1 3128]
E: Failed to fetch https://security.ubuntu.com/ubuntu/dists/noble/InRelease Unsupported proxy scheme
ところが curl では archive.ubuntu.com に正常到達できます。そこで apt を諦め、必要な .deb を curl で直接ダウンロードし、dpkg-deb -x で作業フォルダに展開して、システムにインストールせずに使う形に切り替えました。
# 実行例(概念)
curl -O http://archive.ubuntu.com/ubuntu/pool/main/o/openssh/...deb
dpkg-deb -x openssh-server_*.deb rootfs/
つまずき2:sshd が起動を拒否する
展開した sshd を起動すると即死します。
/run/sshd must be owned by root and not group or world-writable.
特権分離用ディレクトリ /run/sshd の所有者・権限が要件(root:root、グループ/他者書き込み不可)を満たしていなかったのが原因で、chown root:root + chmod 755 で解決しました。地味ですが、sshd はこういう前提条件をきっちり見てきます。
トンネル側の仕様:無料枠は60分で切れる
Pinggy の無料トンネルは、接続時にこう表示されます。
You are not authenticated.
Your tunnel will expire in 60 minutes. Upgrade to Pinggy Pro to get unrestricted tunnels.
セッションは60分で切れ、張り直すたびに公開アドレス(ホスト名とポート)が変わります。なので運用はこうなりました。
- supervisor スクリプトが sshd とトンネルを常駐監視し、落ちたら自動で張り直す
- 現在有効なアドレスはファイル(CURRENT-ADDRESS.txt)に記録し、接続時はその都度それを見る
- アドレスは使い捨て前提。ブックマークや固定設定はしない
接続自体は普通の ssh / scp / sftp がそのまま使えます。
# 形式のみ(実際のアドレス・ポートは都度変わります)
ssh -i <接続用鍵> -p <ポート> root@<現在のアドレス>
自宅 PC からの初回接続テストで踏んだもの
プログラム側ができても、実地テストでは別の罠が3つありました。
罠1:秘密鍵が無視される
WARNING: UNPROTECTED PRIVATE KEY FILE! ...
This private key will be ignored.
→ 続いて Permission denied (publickey)。ダウンロードフォルダに置いたままの鍵は、Windows 側のアクセス権が緩く、OpenSSH が「安全でない」と判断して鍵を使ってくれません。鍵を $HOME\.ssh\ へコピーし、権限を本人のみ読み取りに絞って解決しました。
icacls "$HOME\.ssh\<接続用鍵>" /inheritance:r /grant:r "$env:USERNAME`:R"
ついでに、鍵ファイルの置き場所そのものも間違えていて(ダウンロードフォルダが別ドライブ側だった)Identity file ... not accessible も踏みました。鍵が読めない系のエラーは「パスの正しさ」と「権限の厳しさ」の2点チェックが近道です。
罠2:ログインできるのにポートフォワードだけ失敗する
SSH ログインは成功するのに、-L 転送先の Web UI を開くと ERR_CONNECTION_RESET。sshd のログには次が残っていました。
refused local port forward
原因は、自作 sshd_config に書いた AllowTcpForwarding no。シェル専用に堅牢化したつもりが、この構成の目的(転送経由で VM 内の LLM や管理画面を使う)と真っ向から矛盾していました。AllowTcpForwarding local に変更して sshd を再起動し、転送経由の疎通まで確認して解決。「ログインできるのに転送だけ死ぬ」ときは、クライアントではなく sshd 側の転送許可とログを見るのが正解でした。
罠3:(解決後)最終確認
修正後、SSH ログイン、ポートフォワード経由の Web UI 表示(ローカル LLM の画面と自作ツールのデモ画面)、LLM の応答受信まで一通り確認でき、接続テスト完了となりました。
運用上の注意
- VM が再配置・再起動されると常駐プロセスは全部止まります(ファイルは残る)。復旧は supervisor の起動と、sshd 用の特権分離ユーザー・
/run/sshdの再作成から - 接続できるのは鍵を持つ人だけですが、公開アドレス自体も秘匿情報として扱うこと。記事や SNS に生アドレスを貼らない
- 恒常運用したくなったら、固定アドレスが得られる有料プランや自前ドメイン経由の方式を検討する段階です
未検証事項・調査中
- トンネルの短命化:アイドル時は公称どおり約60分継続する一方、実際に接続して使っていた時間帯には 2〜8分で切断が繰り返された実測があります。高負荷時間帯(VM の load 3 超、空きメモリ 300MB 前後)と重なることまでは分かっていますが、原因は未確定です。切り分けのため、トンネルのセッションごとにログと終了コードを残す計測を仕込んで観測中です
- Pinggy 側エンドポイントへの TLS ハンドシェイクが無応答になり、そもそも接続を開始できない時間帯も観測しています(こちら側の負荷とは無関係な外部要因とみられる)
まとめ
- 入口のない VM でも、内側から張るリバーストンネル+ループバック限定 sshd で、安全側に寄せた SSH 経路は作れる
- 壁は方式そのものより、apt が使えない・鍵権限・転送許可のような「周辺の前提条件」に集中していた
- 無料枠の60分制限は、supervisor による自動再接続と「アドレスは使い捨て」という運用設計で吸収できる
参考資料
- Pinggy 公式サイト: https://pinggy.io (2026-10-30 確認)
- OpenSSH 公式サイト: https://www.openssh.com (2026-10-30 確認)