Guide

スマホから踏み台(bastion)経由で本番サーバーに SSH する方法

答えは ProxyJump(ssh -J)です。エージェント転送は使わないでください。ProxyJump なら秘密鍵は手元のスマホに留まり、踏み台は暗号化されたバイト列を中継するだけで、最終ホストとの間はエンドツーエンドで暗号化されます。iPhone / Android の SSH アプリでは、この機能は「Jump Host」「ホストチェーン」「Proxy」といった名前で用意されています。

CC Chen Chen· Founder·2026-08-12·12 分で読めます

結論:ProxyJump 一択

スマホから踏み台(bastion / jump host)経由で本番サーバーに SSH するときの正解は ProxyJump(ssh -J)です。 秘密鍵は手元のスマホに留まったまま、踏み台は暗号化されたバイト列を中継するだけになり、最終ホストとの間には独立した end-to-end の SSH セッションが張られます。踏み台が侵害されていても、あなたの鍵は使われません。

やってはいけないのはエージェント転送(ssh -A / ForwardAgent yes)です。 これは踏み台の上に「あなたの鍵で署名させられる窓口」を開く操作で、踏み台の root 権限を持つ者は誰でもそれを乗っ取れます。

コマンドは1行です。

ssh -J [email protected] [email protected]

ProxyJump と -J は OpenSSH 7.3(2016年8月1日リリース) で追加されたオプションで(OpenSSH 7.3 release notes)、それ以前に必要だった ProxyCommand + netcat の組み合わせはもう不要です。踏み台側に nc を入れる必要もありません。

iPhone / Android の SSH アプリでは、この機能は「Jump Host」「ホストチェーン」「Proxy」といった名前で GUI に出ています。Termius の場合は「Host Chain」がこれにあたり、公式ドキュメントも ssh の -J 引数および ~/.ssh/config の ProxyJump と同等の機能だと明記しています(Termius docs)。

以下、なぜエージェント転送が危険なのか、ProxyJump が内部で何をしているのか、スマホ側の具体的な設定、モバイル特有の落とし穴、そして踏み台側で締めておくべき設定を順に説明します。

なぜエージェント転送ではダメなのか

「踏み台に入ってから、そこで ssh を打って本番に入る」という運用は、必然的にエージェント転送を要求します。踏み台には鍵が無いので、手元の鍵を貸すしかないからです。ここが問題の根源です。

エージェント転送を有効にすると、踏み台上に UNIX ドメインソケットが作られ、SSH_AUTH_SOCK 環境変数がそこを指します。鍵素材そのものはコピーされませんが、そのソケットに話しかけられる者は誰でも「あなたの鍵で署名してください」と依頼できます。そして踏み台の root は、稼働中の全ユーザーのソケットを列挙して SSH_AUTH_SOCK を差し替えるだけで、あなたがログインしている間、あなたになりすませます。

これは理論上の懸念ではありません。2019年4月11日の Matrix.org の侵害 では、侵害されたホストにログインした開発者の転送されたエージェントが攻撃の横展開に使われました。事後対応として Matrix.org は、本番へのアクセスを SSH 踏み台のみに限定する構成へ移行しています。

OpenSSH の man ページ自体が、ForwardAgent の説明でエージェント転送は注意して有効にすべきだと述べ、より安全な代替として jump host(-J)の利用を挙げています。

ProxyJump との違いを整理すると次のようになります。

エージェント転送 (-A)ProxyJump (-J)
認証が行われる場所踏み台から手元のエージェントへ依頼すべて手元の端末上
踏み台が侵害されたときあなたの鍵で任意のサーバーへ署名される暗号文しか見えない。何もできない
踏み台に残るもの使用可能なエージェントソケットTCP チャネル1本のみ
最終ホストとの暗号化踏み台で一度復号される(ホップごとに別セッション)end-to-end。踏み台は中身を読めない
踏み台で必要なものシェル、ssh クライアントシェル不要(AllowTcpForwarding のみ)

最後の行が地味に効きます。ProxyJump は踏み台上でシェルを起動しないため、踏み台のアカウントを /usr/sbin/nologin にしたまま中継専用にできます。踏み台に対話ログインできる人を1人も作らずに運用できる、ということです。

ProxyJump が実際にやっていること

内部の挙動を知っておくと、後述のエラーの切り分けが一瞬で終わります。ssh -J bastion target を打つと、クライアントは次の順で動きます。

  1. 踏み台へ SSH 接続する。 ここでホスト鍵の検証と、踏み台用の鍵での認証が行われます。
  2. 踏み台のセッション内に direct-tcpip チャネルを1本開く。 「そこから 10.0.1.10:22 へ TCP を張って、中身をこちらへ流してくれ」と依頼します。踏み台にとっては単なるポートフォワードです。
  3. そのチャネルを素の通信路として、最終ホストと SSH ハンドシェイクをやり直す。 鍵交換も認証も、最終ホストとあなたのスマホの間で直接行われます。

ここから3つの実務的な帰結が出ます。

  • 鍵は端末を出ません。 最終ホストへの認証を手元でやるので、踏み台に鍵を置く必要も貸す必要もありません。
  • 踏み台は中身を読めません。 2段目のセッションは1段目の中でさらに暗号化されているためです。踏み台に平文ログを取られる心配は不要です。
  • 踏み台には nc もシェルも要りませんが、AllowTcpForwarding は必要です。 ここが唯一の依存点で、後述の最頻出エラーの原因でもあります。

なおホスト鍵は2つ検証されます。踏み台のものと最終ホストのものです。スマホから初回接続すると確認プロンプトが2回出ますが、これは正常です(ホスト鍵と TOFU)。

スマホ側の設定:config 方式と GUI 方式

config ファイルを編集できるアプリの場合

~/.ssh/config を持てるクライアント(Blink Shell、Termux 上の OpenSSH、その他 config をインポートできるアプリ)なら、デスクトップとまったく同じ設定が動きます。

Host bastion
    HostName bastion.example.com
    User ops
    Port 22
    IdentityFile ~/.ssh/id_ed25519_bastion
    IdentitiesOnly yes

Host prod-web prod-db 10.0.1.*
    User deploy
    ProxyJump bastion
    IdentityFile ~/.ssh/id_ed25519_prod
    IdentitiesOnly yes
    ServerAliveInterval 30
    ServerAliveCountMax 3

これで ssh prod-web の1コマンドが2ホップに展開されます。ポイントは3つです。

  • IdentitiesOnly yes を必ず書く。 書かないとクライアントは手持ちの鍵を順に試し、踏み台側で Too many authentication failures を食らって切断されます(原因と対処)。鍵を数本持ちがちなスマホでは特に踏みやすい罠です。
  • ServerAliveInterval を設定する。 モバイル回線では NAT のタイムアウトでセッションが黙って死にます。踏み台経由だと死ぬ箇所が2倍あるので、ここは必須です。
  • 例外は ProxyJump none で外す。 踏み台と同じネットワークにいるときだけ直結したい、といった場合に使います。

複数段は ProxyJump bastion1,bastion2 とカンマで並べます。ホップごとにユーザーやポートが違うなら -J ops@bastion1:2222,jump@bastion2 の形で個別指定できます。config の書き方全般は SSH config ファイルの書き方 にまとめています。

GUI しかないアプリの場合

ホスト編集画面に「Jump Host」「Host Chain」「Proxy」「経由ホスト」といった項目があるので、そこに踏み台として登録済みのホストを指定します。手順は共通してこうなります。

  1. まず踏み台を1つのホストとして登録する(ホスト名・ユーザー・ポート・踏み台用の鍵)。
  2. 次に本番サーバーをホストとして登録する。HostName には踏み台から見た到達先(多くの場合 10.0.1.10 のようなプライベート IP、または内部 DNS 名)を書きます。ここを外部から見えるアドレスにしてしまうのが典型的なミスです。
  3. その本番ホストの「Jump Host」欄で、1で作った踏み台を選ぶ。
  4. 本番ホストには本番用の鍵を割り当てる(踏み台用とは別の鍵。次節参照)。

グループ単位でチェーンを設定できるアプリもあり、本番系のホストをまとめて1つのグループに入れておくと、以後追加するホストは設定を継承します。サーバーが増えたときに効いてきます。

鍵の分け方:踏み台用と本番用

踏み台用と本番用は別の鍵にしてください。 同じ鍵を両方に登録しても動きますが、分けておくと権限設計と失効が段違いに楽になります。

推奨する構成は次の通りです。

  • 鍵はスマホ側で新規に生成する。 PC の鍵をコピーしてこないでください。コピーの経路(クリップボード、メール、クラウド)がそのまま新しい漏洩点になります。手順は iPhone で SSH 鍵を作成する方法 に。
  • コメントに端末名を入れる。 iphone-termai-bastion のようにしておくと、authorized_keys が監査可能な一覧になり、端末を失くしたときに消す行が一目で分かります。
  • 踏み台用の鍵には対話シェルを与えない。 中継しかしないなら、踏み台側でその鍵に restrict,permitopen="10.0.1.10:22" を付けます。これでその鍵は「指定した1台の22番へ TCP を中継する」以外に何もできなくなります。
  • 本番用の鍵は権限を絞る。 スマホから必要なのはログ閲覧とサービス再起動くらいのはずで、本番全台に root で入れる万能鍵をスマホに持ち込むのは保管方式に関係なく設計ミスです。

鍵をスマホに置くこと自体の安全性(端末内生成、iOS Keychain / Android Keystore、エクスポート不可)については SSH 秘密鍵をスマホに置くのは安全か で詳しく扱っています。要点だけ言えば、エクスポートできない鍵ストアに端末内生成で入っているなら、ノートPCの ~/.ssh に平文で置くより安全です。

公開鍵の登録手順は ssh-copy-id の使い方 を参照してください。踏み台越しの最終ホストには ssh-copy-id -o ProxyJump=bastion [email protected] の形で流し込めます。

モバイル特有の落とし穴4つ

デスクトップの手順をそのまま持ってくると詰まる箇所が4つあります。

1. 遅延がホップ数だけ積み上がる。 モバイル回線の RTT は良くて 30〜50ms、混雑時は 200ms を超えます。踏み台を1段挟むとキー入力の反射がその分遅れ、2段挟めば体感でわかるほど遅くなります。段数は最小限にし、遅い時は入力補完やスニペットで打鍵数そのものを減らすのが現実解です。

2. 回線が切り替わると2段とも死ぬ。 Wi-Fi からモバイルデータへ移った瞬間、1段目の TCP が切れ、その上に乗っている2段目も道連れになります。対策は最終ホスト側の tmux です。接続が切れても作業は生き残り、再接続して tmux attach で戻れます。長時間の処理を投げるときは必須です(SSH 切断後もプロセスを生かす)。

3. Mosh は踏み台越しには素直に通らない。 Mosh はセッション確立に SSH を使いますが、その後は UDP(既定で 60000〜61000番)でクライアントとサーバーが直接やり取りするため、SSH の TCP トンネルの中を通りません。回線切り替えに強い接続がほしいなら、Mosh より Tailscale で最終ホストに直接到達させるほうが素直です(Mosh vs SSH)。

4. 接続元 IP が固定できない。 踏み台の authorized_keys を from= で絞る運用は堅いのですが、モバイル回線ではキャリア側の IP が変わり続けるため、そのままでは使えません。オーバーレイネットワーク経由にして from="100.64.0.0/10" のようにその帯域だけ許可する、というのが現実的な折衷案です。

よくあるエラーと切り分け

踏み台経由の失敗はどちらのホップで落ちたかさえ分かれば大半が解決します。切り分けの第一手は、踏み台単体に接続してみることです。

ssh -v [email protected]

これが通るなら1段目は健全で、問題は2段目にあります。詳細ログは ssh -vv -J ... で両方のハンドシェイクが順に出るので、どちらで止まったかがそのまま読めます。

症状落ちている場所原因と対処
channel 0: open failed: administratively prohibited: open failed踏み台最頻出。 踏み台の AllowTcpForwarding no、または鍵に no-port-forwarding / restrict が付いている。AllowTcpForwarding yes にするか、鍵に permitopen="10.0.1.10:22" を追加してもらう
踏み台には入れるが最終ホストで Connection timed out2段目最終ホストの HostName を外部アドレスで書いている、または踏み台から最終ホストへの経路・セキュリティグループが閉じている。踏み台上で nc -vz 10.0.1.10 22 を試す(接続タイムアウトの原因)
Too many authentication failures主に1段目鍵を総当たりしている。IdentitiesOnly yes と明示的な IdentityFile を設定する
Permission denied (publickey)ログを見て判別どちらのホップの鍵が違うのかを -vv で確認。踏み台用と本番用を取り違えているケースが多い(原因の切り分け)
ホスト鍵の警告が2回出る正常踏み台と最終ホストの両方を検証するため。初回は両方確認する(Host key verification failed)
接続後しばらくして無反応になるどちらでも起きるモバイル回線の NAT タイムアウト。ServerAliveInterval 30 を設定する

汎用の切り分け手順は SSH トラブルシューティング にまとめています。

踏み台側で締めておく設定

クライアント側だけで完結させないのが実務です。踏み台を用意する側なら、最低限これだけは入れておきます。

中継専用の鍵は restrict + permitopen で縛る。 踏み台の ~/.ssh/authorized_keys はこう書けます。

restrict,permitopen="10.0.1.10:22" ssh-ed25519 AAAAC3Nz... iphone-termai-bastion

restrict は「デフォルトで全部禁止」を意味し、そこに permitopen で必要な転送先だけを穴として開けます。この鍵ではシェルもエージェント転送も X11 転送も使えず、指定した1台の22番へ TCP を中継することしかできません。なお restrict の適用範囲は改善が続いており、OpenSSH 10.5 では restrict がトンネル転送にも適用されるよう修正されています(OpenSSH release notes)。踏み台は最新版に追従させておく価値があります。

踏み台のアカウントにシェルを与えない。 ProxyJump は踏み台上でシェルを起動しないので、/usr/sbin/nologin のままで中継は成立します。対話ログインできる人を作らなければ、踏み台上での横展開の余地が消えます。

root 直ログインを禁止する。 PermitRootLogin no は踏み台・最終ホストの両方で前提条件です(root ログインの無効化)。

パスワード認証を切る。 鍵での接続が別セッションで確実に通ることを確認してから PasswordAuthentication no にします。インターネットに面する踏み台では特に効きます。

踏み台のログを本気で取る。 踏み台を置く目的の半分は監査です。journalctl -u ssh が誰の鍵でいつ入ったかを記録できるよう、鍵ごとにコメントを付け、LogLevel VERBOSE にしてフィンガープリントをログに残します。

そもそも踏み台をやめる選択肢

既存の踏み台があり、監査要件で全アクセスを1点に集約する必要があるなら、ProxyJump で使い続けてください。踏み台を経由すること自体が鍵のセキュリティを下げることはありません。

一方、構成を自分で決められるなら、モバイル運用には踏み台を持たない選択肢のほうが向いています。

  • Tailscale などのオーバーレイネットワーク。 22番をインターネットに公開せず、踏み台という単一障害点も持たず、ホップが1段減るので遅延も小さくなります。アクセス制御は ACL で行い、端末を失くしたら管理コンソールでそのデバイスを失効させるだけです。CGNAT 配下の自宅サーバーにも同じ仕組みで入れます(Tailscale SSH の使い方)。
  • AWS EC2 Instance Connect Endpoint。 2023年6月に提供が始まった、パブリック IP も踏み台インスタンスも持たずに VPC 内の EC2 へ SSH / RDP するための仕組みで、通信は AWS のコントロールプレーンを通ります(AWS announcement)。踏み台インスタンスのパッチ適用・監視・課金がまるごと消えるのが利点です。ただし AWS CLI に依存するため、スマホ単体からは使いにくい構成です。
  • SSM Session Manager。 同様に踏み台を不要にしますが、こちらも実質的に AWS CLI 前提です。

スマホからの実運用という観点では、「Tailscale で最終ホストに直接到達」が最も単純で、失敗する箇所が最も少ない構成になります。組織の要件で踏み台が外せないなら ProxyJump、外せるなら Tailscale、というのが選択の基準です。

TermAI での運用

TermAI は iOS / Android の SSH・SFTP クライアントで、Tailscale を内蔵しています。つまり、踏み台を経由する構成と、そもそも踏み台を挟まない構成の両方を同じアプリの中で扱えます。踏み台経由が必要な環境では、踏み台をホストとして登録し、本番ホスト側でそれを経由先として指定する、前述の GUI 方式の手順になります。ホスト一覧の作り方全体は スマホからサーバーを管理する を参照してください。

鍵は端末内で生成またはインポートされ、iOS Keychain / Android Keystore に保管されます。秘密鍵がサーバーに送信されることはなく、これは ProxyJump の設計思想 ――鍵を手元から出さない―― とそのまま噛み合います。エージェント転送を避ける理由も、鍵をエクスポート不可の鍵ストアに入れる理由も、突き詰めれば同じ「鍵を使える場所を1箇所に閉じ込める」という一点です。

AI 機能は意図的に提案型です。journalctl -u nginx --since "10 min ago" のようなコマンドを入力行に書くだけで、実行するかどうかは人が判断します。踏み台の向こうにある本番サーバーで、確認なしにコマンドが走る設計は選んでいません(AI のシェル提案をどこまで信じるか)。無料枠は AI 提案が1日5回、SSH と SFTP は無制限です。

よくある質問

TermAI を試す

iOS / Android で無料。無料プランは AI 5回/日、SSH/SFTP 無制限、Tailscale 内蔵。

CC
Chen Chen — Founder of TermAI

Writes about mobile DevOps, terminal UX, and the surprising depth of "boring" infrastructure.