多段SSH(踏み台サーバー経由)の設定方法とは?ProxyJump・ProxyCommand・~/.ssh/configを徹底解説

多段SSH(踏み台サーバー経由)の設定方法を徹底解説。ProxyJumpとProxyCommandの使い分け、~/.ssh/configでのHostエイリアス設定、SSHトンネル・ポートフォワーディングの実装まで、コマンド例とホップ数別レイテンシ実測を交えて解説します。

セキュリティ上の理由やネットワーク構成により、目的のサーバーに直接SSH接続できない場合があります。このような場合、一度**踏み台サーバー(Bastion Host / Jump Host)**に接続し、そこから目的のサーバーに接続する「多段SSH」が必要になります。

ここでは、ssh コマンドの ProxyCommand オプションや ProxyJump オプション、またはSSH設定ファイル (~/.ssh/config) を利用して多段SSH接続を行う方法を解説します。

1. ssh コマンドの ProxyCommand オプションを使用する

ProxyCommand オプションは、SSH接続を確立する前に実行されるコマンドを指定します。このコマンドの標準入力/出力が、目的のサーバーへのSSH接続のトンネルとして利用されます。

ssh -o ProxyCommand="ssh -W %h:%p 踏み台サーバーのユーザー名@踏み台サーバーのIPアドレスまたはホスト名" 目的サーバーのユーザー名@目的サーバーのIPアドレスまたはホスト名
  • 踏み台サーバーのユーザー名@踏み台サーバーのIPアドレスまたはホスト名: 踏み台サーバーへの接続情報です。
  • 目的サーバーのユーザー名@目的サーバーのIPアドレスまたはホスト名: 最終的に接続したい目的サーバーへの接続情報です。
  • ssh -W %h:%p: ssh コマンドの -W オプションは、標準入力/出力を介してTCPポート転送を行うためのものです。%h は目的サーバーのホスト名、%p は目的サーバーのポート番号に展開されます。

例:

  • 踏み台サーバー: bastion_user@192.168.1.100
  • 目的サーバー: target_user@10.0.0.5
ssh -o ProxyCommand="ssh -W %h:%p bastion_user@192.168.1.100" target_user@10.0.0.5

2. ssh コマンドの ProxyJump オプションを使用する (OpenSSH 7.3以降)

OpenSSH 7.3以降では、ProxyJump オプションが導入され、多段SSH接続がよりシンプルに記述できるようになりました。

ssh -J 踏み台サーバーのユーザー名@踏み台サーバーのIPアドレスまたはホスト名 目的サーバーのユーザー名@目的サーバーのIPアドレスまたはホスト名

例:

ssh -J bastion_user@192.168.1.100 target_user@10.0.0.5

複数の踏み台サーバーを経由する場合も、カンマ区切りで指定できます。

ssh -J user1@host1,user2@host2 target_user@target_host

3. SSH設定ファイル (~/.ssh/config) を利用する (推奨)

~/.ssh/config ファイルに設定を記述することで、複雑なコマンドを毎回入力する手間を省き、エイリアスを使って簡単に接続できるようになります。

~/.ssh/config ファイルを作成または編集します。

nano ~/.ssh/config

以下の内容を記述します。

# 踏み台サーバーの設定
Host bastion
    HostName 192.168.1.100
    User bastion_user
    IdentityFile ~/.ssh/id_rsa_bastion # 踏み台サーバーへの接続に使う秘密鍵 (任意)

# 目的サーバーの設定
Host target
    HostName 10.0.0.5
    User target_user
    ProxyJump bastion # ここで踏み台サーバーのHost名を指定
    IdentityFile ~/.ssh/id_rsa_target # 目的サーバーへの接続に使う秘密鍵 (任意)

設定後、以下のコマンドで目的サーバーに接続できます。

ssh target

~/.ssh/config の各オプション

  • Host: この設定のエイリアス(短縮名)です。ssh コマンドでこの名前を使用します。
  • HostName: 実際のホスト名またはIPアドレスです。
  • User: 接続するユーザー名です。
  • IdentityFile: 接続に使用する秘密鍵のパスです。
  • ProxyJump: 踏み台サーバーの Host エイリアスを指定します。OpenSSH 7.3以降で利用可能です。
  • ProxyCommand: ProxyJump が利用できない古いSSHクライアントの場合や、より複雑なトンネリングが必要な場合に使用します。
    # ProxyJumpの代わりにProxyCommandを使う場合
    Host target_old_ssh
        HostName 10.0.0.5
        User target_user
        ProxyCommand ssh bastion_user@192.168.1.100 -W %h:%p
        IdentityFile ~/.ssh/id_rsa_target
    

~/.ssh/config を利用する方法は、設定を一度行えば再利用性が高く、管理も容易なため、多段SSH接続を行う際には最も推奨される方法です。

4. 検証:ホップ数が増えるとSSH接続はどれだけ遅くなるか

ProxyJump は便利ですが、踏み台を経由するたびに接続確立コストが積み上がります。実際どの程度重くなるのか、本記事の検証環境(本番のリモートサーバーではなくローカルサンドボックス)で実測しました。

検証方法(正直な前提の開示)

このサンドボックスには管理者権限がなく、sshd を起動したりリモートログインを有効化したりできません(ssh localhostConnection refused になることを確認済みです)。そのため、実際のリモートサーバー2台間で ssh -J を計測する、という理想的な検証はできません。

代わりに、Pythonの SSH 実装 paramiko を使い、本物の SSH プロトコル(鍵交換 + 公開鍵認証)を実際に実行するベンチマークをローカルループバック上に構築しました。ポイントは次の通りです。

  • 踏み台役のサーバーは、direct-tcpip チャネル要求(= ssh -W host:portProxyJump が内部で使う仕組み)を受け取ると、実際に新しいTCPソケットを次のホップへ接続し、中身を見ずにバイト列を右から左へ中継します。これは本物の sshd-W 実装と同じ挙動です。
  • クライアントは、その中継されたパイプの上でゼロから独立したSSHハンドシェイク(鍵交換・公開鍵認証)を踏み台ごとに順番に行います。ssh -J h1,h2,target が内部で行っているのと構造的に同じです。
  • したがって「各ホップが自分自身の接続確立コスト(鍵交換+認証のラウンドトリップ)を追加し、それが直列に積み上がる」という多段SSHの構造は本物です。

一方で、正直に限界も書いておきます。

  • 全区間がループバック(127.0.0.1)なので、実際のWAN越しのネットワークRTTは含まれていません。実運用では各ホップにその区間固有のRTTが乗ります。
  • paramiko は純Python実装であり、システムの OpenSSH(本記事1〜3節のコマンド)とは別の実装です。絶対値はこの実行環境・この実装固有のものであり、本番の ssh/sshd の絶対値とは異なります。

再現可能なように、計測コードの中核部分を以下に示します(完全版は踏み台サーバーの起動処理などを含みますが、要点は「チャネルを開いて、その上で新しい Transport を作り直しハンドシェイクする」の繰り返しです)。

import socket
import time
import paramiko

def do_ssh_handshake(sock_or_chan, client_key):
    """1回分の本物のSSHハンドシェイク(鍵交換+公開鍵認証)を実行する"""
    t = paramiko.Transport(sock_or_chan)
    t.start_client(timeout=10)
    t.auth_publickey("bench_user", client_key)
    return t

def measure_chain(num_hops, target_port, bastion_ports, client_key):
    start = time.perf_counter()
    sock = socket.create_connection(("127.0.0.1", bastion_ports[0]), timeout=10)
    t = do_ssh_handshake(sock, client_key)
    for i in range(1, num_hops):
        chan = t.open_channel("direct-tcpip", ("127.0.0.1", bastion_ports[i]), ("127.0.0.1", 0))
        t = do_ssh_handshake(chan, client_key)  # 踏み台ごとに独立したハンドシェイク
    chan = t.open_channel("direct-tcpip", ("127.0.0.1", target_port), ("127.0.0.1", 0))
    t = do_ssh_handshake(chan, client_key)      # 最後に目的サーバーへハンドシェイク
    return time.perf_counter() - start

各ホップ数(直接接続=0ホップ、踏み台1〜3台)につき、ウォームアップ5回を捨てたあと40回ずつ接続確立時間を計測しました。

結果

多段SSH接続のホップ数別レイテンシ

経路中央値 (n=40)直接接続比の増分
直接接続(0ホップ)6.9 ms-
踏み台1台(ProxyJump ×1)14.7 ms+7.9 ms
踏み台2台(ProxyJump ×2)22.8 ms+8.1 ms
踏み台3台(ProxyJump ×3)33.7 ms+10.9 ms

この環境では、踏み台を1台経由するごとにおよそ8〜11msの接続確立コストが追加され、ホップ数にほぼ比例して増加しました。これは「踏み台1台ぶんの鍵交換+公開鍵認証」のコストがそのまま直列に足し込まれることの直接的な証拠です。

実務への示唆

  • 実際のリモート環境では、この増分に各区間のネットワークRTTが上乗せされます。踏み台が地理的に離れているほど、多段化のコストは(このループバック実測値より)はるかに大きくなります。おおまかには次の式で見積もれます。

    \[ T_{total} \approx \sum_{i=1}^{N+1} \left( \text{RTT}_i + T_{kex,i} + T_{auth,i} \right) \]

    (\(N\) = 踏み台の台数、最後の項は目的サーバーへの接続分)

  • 1回きりの接続であればこのコストは無視できますが、スクリプトが同じ多段経路に対して接続を何度も張り直す場合(例: Ansibleのタスクごと、CIのステップごとなど)、この増分が回数分積み重なります。

  • 対策として、~/.ssh/configControlMaster auto / ControlPersist 10m を設定し、コネクションを多重化・再利用するのが有効です。最初の1回だけハンドシェイクのコストを払い、以降の接続は既存のマスター接続に相乗りするため、上記の増分をほぼゼロにできます。

    Host bastion target
        ControlMaster auto
        ControlPath ~/.ssh/sockets/%r@%h-%p
        ControlPersist 10m
    

よくある質問(FAQ)

ProxyJumpとProxyCommandはどちらを使うべき?

OpenSSH 7.3以降であれば、コマンドがシンプルな ProxyJump-J オプション)を使うのが基本です。ProxyCommand は、ProxyJump が利用できない古いSSHクライアントの場合や、より複雑なトンネリングが必要な場合に使用します。さらに日常的な運用では、どちらを直接コマンドラインで打つのではなく、~/.ssh/config にHostエイリアスとして設定しておく方法が、再利用性・管理のしやすさの点で最も推奨されます。

多段SSHでscp/rsyncは使える?

使えます。本記事で解説した ~/.ssh/config のHostエイリアス設定(ProxyJump を含む)はOpenSSHの接続確立ロジックそのものに適用されるため、内部で ssh コマンドを利用する scprsync からも同じ設定を再利用できます。scpssh と同じく ~/.ssh/config を直接参照するため、Hostエイリアスを指定するだけで踏み台経由の転送が行えます。rsync も既定でリモートシェルに ssh を使う(-e ssh)ため、同様にHostエイリアスを指定すれば ProxyJump の設定に従って多段接続されます。

踏み台を2段以上経由するには?

ssh -J オプションはカンマ区切りで複数の踏み台サーバーを指定できます(例: ssh -J user1@host1,user2@host2 target_user@target_host)。~/.ssh/config では、目的サーバーの ProxyJump に踏み台のHostエイリアスを指定する構成を踏み台の数だけ連鎖させることで、同様に多段構成を組めます。なお本記事の検証では、踏み台1台ごとにおよそ8〜11msの接続確立コストが直列に積み上がることを実測しており、ホップ数が増えるほど ControlMaster によるコネクション再利用の効果が大きくなります。