第25章 SSHの設定と運用

LPIC-2 212.3 相当

SSHはリモート管理・ポート転送・様々なサービスの管理基盤として、LPIC-2 212.3の中でも特に実務での使用頻度が高い技術です。この章では、プロトコルの内部構造から、鍵認証の仕組み、サーバー側の設定(sshd_config)、ポートフォワーディング、そして設定変更時に自分自身を締め出さないための安全な運用手順までを、実際のコマンドと出力例を交えて扱います。

SSHプロトコルの構造

SSHプロトコル(SSH-2)は、役割の異なる3つの層が積み重なって構成されています。

層役割
トランスポート層サーバーとクライアント間で鍵交換を行い、以後の通信を暗号化する。ホスト鍵によるサーバー認証もこの層で行われる
ユーザー認証層公開鍵認証・パスワード認証など、接続してきたユーザーが本人かどうかを確認する
コネクション層認証が完了した1本のSSH接続の中で、シェルセッション・ポートフォワード・X11転送・SFTPなど複数の「チャネル」を多重化して同時に扱う

この階層構造のおかげで、1回の接続・1回の認証だけで、通常のシェルログインと同時にポートフォワードを張ったり、複数のコマンドを並行実行したりできます。ssh -Lやssh -Rによるポートフォワードが、シェルログインとは別の「チャネル」として同じ接続の中に多重化されているのは、このコネクション層の仕組みによるものです。

ホスト鍵の役割としくみ

SSHサーバーは、自分自身を証明するための「ホスト鍵」を持ちます。クライアントはこのホスト鍵を検証することで、接続先が本当に意図したサーバーであり、中間者攻撃(Man-in-the-Middle)を受けていないことを確認します。ホスト鍵は/etc/ssh/以下に鍵種別ごとに複数保存されています。

$ ls /etc/ssh/ssh_host_*
/etc/ssh/ssh_host_ecdsa_key       /etc/ssh/ssh_host_ecdsa_key.pub
/etc/ssh/ssh_host_ed25519_key     /etc/ssh/ssh_host_ed25519_key.pub
/etc/ssh/ssh_host_rsa_key         /etc/ssh/ssh_host_rsa_key.pub

クライアントが初めて接続したサーバーのホスト鍵は~/.ssh/known_hostsに記録され、以後の接続ではこの記録と照合されます。サーバーの再構築やホスト鍵の再生成によって、以前記録したものと異なるホスト鍵が返ってくると、次のような強い警告が表示されます。

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
Offending ECDSA key in /home/alice/.ssh/known_hosts:12
よくある間違い: この警告が出た際に、原因を確認せず反射的にknown_hostsのエントリを削除して接続してしまうケースが見られます。サーバーの再構築・OS再インストール・IPアドレスの再利用など正当な理由がある場合もありますが、それを確認する前にエントリを削除するのは、実際に中間者攻撃を受けている場合にそれを見逃すことになり、本来の警告の意味を失わせます。まずサーバー管理者に鍵が変わった心当たりを確認し、正当な理由が確認できてから対処してください。

正当な理由が確認できた場合、古いエントリだけを安全に削除するにはssh-keygen -Rを使います。known_hostsファイル全体を手で編集するより安全で確実です。

$ ssh-keygen -R server.example.com
# Host server.example.com found: line 12
/home/alice/.ssh/known_hosts updated.
Original contents retained as /home/alice/.ssh/known_hosts.old

逆に、新しいサーバー群を構築する際に、事前にホスト鍵を収集してknown_hostsへ登録しておきたい場合はssh-keyscanが使えます。初回接続時の「本当にこのホストに接続しますか?」という確認プロンプトを省略できるため、自動化されたプロビジョニング処理に組み込まれることがよくあります。

$ ssh-keyscan server1.example.com server2.example.com >> ~/.ssh/known_hosts
# server1.example.com:22 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.5
server1.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
# server2.example.com:22 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.5
server2.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
# 複数ホストのホスト鍵をまとめて事前収集し、known_hostsに追記する

この行のこの値に注目: #で始まる行は標準エラー出力に流れる進捗コメントで、そのホストへの接続とバナー取得に成功したことを示します。実際にknown_hostsファイルへ追記されるのは# のないホスト鍵の行だけです。この方法はホスト鍵の正当性を検証せずそのまま信頼するため、中間者攻撃が行われていない、信頼できるネットワーク経路上でのみ使うべきです。

既定ではknown_hostsにホスト名がそのまま平文で記録されますが、ssh_configでHashKnownHosts yesを指定すると、ホスト名部分がハッシュ化されて記録されます。これにより、known_hostsファイル自体が流出しても、そこにどのサーバーへ接続してきたかの一覧を読み取られにくくなります(Debian系ではOpenSSHの既定で有効になっていることが多い設定です)。

公開鍵認証のしくみ

公開鍵認証は、次のようなチャレンジ・レスポンスの流れで行われます。秘密鍵そのものがネットワーク上を流れることはありません。

  1. クライアントが、公開鍵認証を試みたい旨と、対応する公開鍵の情報をサーバーに提示する
  2. サーバーはauthorized_keysを確認し、一致する公開鍵が登録されていれば、そのセッション固有のデータに対する署名をクライアントに要求する
  3. クライアントは、手元の秘密鍵でそのデータに署名し、サーバーへ送り返す
  4. サーバーは、登録済みの公開鍵を使ってその署名を検証する。署名が正しければ、対応する秘密鍵を確かに持っていると証明されたことになり、認証成功となる

鍵ペアの生成と鍵種別の比較

$ ssh-keygen -t ed25519 -C "alice@laptop"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/alice/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/alice/.ssh/id_ed25519
Your public key has been saved in /home/alice/.ssh/id_ed25519.pub

-Cで指定したコメントは、公開鍵ファイルの末尾に付与され、どの用途・端末の鍵かを見分けるために使います。鍵の種類ごとに、鍵長・処理速度・現在の推奨度が異なります。

種類鍵長の目安速度推奨度
RSA3072〜4096bit(2048bitは非推奨傾向)鍵長が長いほど遅くなる互換性重視の場合の選択肢。新規なら4096bit以上を推奨
ECDSA256〜521bitRSAより高速使用可能だが、曲線の選定に懸念を示す声もあり積極採用は少なめ
Ed25519固定256bit相当最も高速かつ署名サイズも小さい現在の事実上の標準。特別な互換性要件がなければ第一候補

authorized_keysの管理

ssh-copy-idは、公開鍵をリモートサーバーに登録する作業を自動化するコマンドですが、実体は単純です。SSH経由でリモートに接続し、~/.ssh/authorized_keysの末尾に公開鍵の内容を追記しているだけです。

$ ssh-copy-id alice@server
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/alice/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
alice@server's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'alice@server'"
and check to make sure that only the key(s) you wanted were added.
# 実際には概ね以下のコマンドと同等の処理が行われている
$ cat ~/.ssh/id_ed25519.pub | ssh alice@server 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'   # 正常時は無出力

この行のこの値に注目: Number of key(s) added: 1が表示されれば登録成功です。すでに同じ鍵が登録済みの場合は0になり、二重登録を防いでくれます。この段階ではまだパスワード認証で接続しているため、パスワードの入力を求められます。

サーバー側では、~/.sshディレクトリとauthorized_keysファイルのパーミッションが厳格でないと、sshdはセキュリティ上の理由で認証を拒否します。これは、グループや他人が書き込み可能な状態だと、他のユーザーが勝手に自分の公開鍵を追記してなりすましログインできてしまう危険があるためです。

対象必要なパーミッション
ホームディレクトリ他人に書き込み権限がないこと(一般に755以下)
~/.ssh700(所有者のみ読み書き実行可)
~/.ssh/authorized_keys600(所有者のみ読み書き可)
$ chmod 700 ~/.ssh                        # 正常時は無出力
$ chmod 600 ~/.ssh/authorized_keys        # 正常時は無出力

authorized_keysでは、各鍵の行の先頭にオプションを付与することで、その鍵ごとにできることを制限できます。委託先や自動化ジョブ専用の鍵など、用途を絞りたい場合によく使われます。

オプション意味
command="..."この鍵でログインした際、指定したコマンド以外を実行できないようにする
from="..."この鍵を使った接続を、指定したIPアドレス/ホスト名からのみ許可する
no-port-forwardingこの鍵でのポートフォワーディングを禁止する
no-ptyこの鍵で対話的な擬似端末(シェル)の割り当てを禁止する
restrictポートフォワード・X11転送・エージェント転送・ptyなど、リスクのある機能をまとめて一括禁止する(新しい機能が追加されても安全側に倒れる)
authorized_keys の1行の例(バックアップジョブ専用の鍵)
restrict,command="/usr/local/bin/backup-sync.sh",from="10.0.5.0/24" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... backup-job

sshd_configの主要ディレクティブ

SSHサーバー(sshd)の設定ファイルは/etc/ssh/sshd_configです。項目数が多いため、用途別の表に分けて整理します。

ディレクティブ意味
Port待ち受けるポート番号(既定は22。複数回指定すると複数ポートで待受可)
ListenAddress待ち受けるネットワークインターフェースのIPアドレス(未指定なら全インターフェース)
AddressFamily使用するアドレスファミリー(any/inet=IPv4のみ/inet6=IPv6のみ)
Protocol使用するSSHプロトコルバージョン。旧式のProtocol 1(脆弱性が多い)は現行のOpenSSHでは既に削除されており、実質SSH-2のみのため、このディレクティブ自体が事実上意味を持たない古い設定項目になっている
ディレクティブ意味
PermitRootLoginrootでの直接ログインの可否。yes/no/prohibit-password/forced-commands-onlyの4値
PubkeyAuthentication公開鍵認証を許可するか(既定でyes)
PasswordAuthenticationパスワード認証を許可するか(鍵認証を導入したらnoにするのが定石)
PermitEmptyPasswords空パスワードでのログインを許可するか(no固定が原則)
ChallengeResponseAuthenticationPAM経由のチャレンジレスポンス認証(ワンタイムパスワード等)を許可するか。新しいOpenSSHではKbdInteractiveAuthenticationに名称が統一されつつある
KbdInteractiveAuthenticationキーボードインタラクティブ認証の許可(ChallengeResponseAuthenticationの後継名)
MaxAuthTries1回の接続で許容する認証試行回数の上限
LoginGraceTime接続確立後、認証が完了するまでの猶予時間。超過すると強制切断

PermitRootLoginの4つの値は意味が異なるため、表で押さえておきましょう。

値意味
yesパスワード・鍵を問わずrootで直接ログイン可(非推奨)
norootでの直接ログインを完全に禁止(最も安全。sudo経由での昇格を利用させる)
prohibit-passwordパスワード認証は禁止するが、公開鍵認証でのrootログインは許可する
forced-commands-onlyauthorized_keysにcommand=オプションで固定コマンドが指定されているrootの鍵に限り、そのコマンド実行のみを許可(バックアップ処理の自動化などに限定利用)
ディレクティブ意味
AllowUsersログインを許可する特定のユーザーをスペース区切りで列挙
DenyUsersログインを拒否する特定のユーザーを列挙
AllowGroupsログインを許可する特定のグループを列挙
DenyGroupsログインを拒否する特定のグループを列挙

これら4つを併用する場合、sshdはDenyUsers → AllowUsers → DenyGroups → AllowGroupsの順に評価します。Denyがいずれかに一致すればその時点で拒否が確定し、Allowが指定されている場合はそのいずれかに一致しない限りログインを許可しません(Allow系のディレクティブが1つでも存在すると、それに明示的に一致しないユーザーは全員拒否される「許可リスト方式」になる点に注意してください)。

ディレクティブ意味
MaxSessions1つのネットワーク接続の中で多重化できるセッション(シェルやポートフォワード等のチャネル)の上限数
MaxStartups認証が完了していない未認証接続を同時に何本まで受け付けるか(DoS対策)
ClientAliveIntervalクライアントへ生存確認パケットを送る間隔(秒)
ClientAliveCountMax応答がない場合に何回まで許容してから切断するか

ClientAliveInterval/ClientAliveCountMaxはSSHプロトコルレベルでの生存確認で、応答がなければサーバー側から能動的に切断できます。これに対しOSのTCPKeepAlive(sshd_configにも同名の設定があります)はTCP層でのキープアライブに過ぎず、経路上のNAT機器などが介在すると検知が効かないことがあり、アイドル接続の確実な検出という点ではClientAlive系の方が信頼性が高いとされています。

ディレクティブ意味
X11ForwardingX11フォワーディング(ssh -X/-Y)を許可するか
AllowTcpForwardingポートフォワーディング全般(-L/-R/-D)を許可するか
GatewayPortsリモートフォワード(-R)で開いたポートを、サーバーのlocalhostだけでなく外部ホストからも接続可能にするか
PermitTunneltunデバイスを使ったVPN的なトンネリング(-w)を許可するか
ディレクティブ意味
Subsystem sftp ...SFTPサブシステムの実体(内蔵のinternal-sftp、または外部の/usr/lib/openssh/sftp-server)を指定
ChrootDirectoryログイン後にそのディレクトリをルートとして閉じ込める(SFTP専用アカウントの隔離によく使用)
注意: ChrootDirectoryで指定するディレクトリ(と、その全ての親ディレクトリ)は、所有者がrootで、かつ他ユーザーが書き込めない状態でなければなりません。この条件を満たさない場合、sshdはセキュリティ上の理由で接続自体を拒否します(fatal: bad ownership or modes for chroot directoryというエラーがログに記録されます)。

Matchブロックによる条件付き設定とSFTP専用アカウント

Matchディレクティブを使うと、特定のユーザー・グループ・接続元アドレスに対してだけ異なる設定を適用できます。代表的な用途が、SFTP専用ユーザーをchroot環境に閉じ込める構成です。

/etc/ssh/sshd_config の抜粋
Subsystem sftp internal-sftp

Match User sftpuser
    ChrootDirectory /srv/sftp/%u
    ForceCommand internal-sftp
    AllowTcpForwarding no
    X11Forwarding no

ChrootDirectory /srv/sftp/%uの%uはログインユーザー名に展開されるトークンです。ForceCommand internal-sftpによって、そのユーザーが実行できる操作をSFTPのファイル転送のみに強制し、通常のシェルを与えません。Matchブロックは設定ファイルの末尾に近い場所に置き、それより後に書かれたディレクティブは、条件に一致したブロックの中でのみ有効になります(Matchは次のMatchまたは設定ファイルの末尾まで有効範囲が続きます)。Match User・Match Group・Match Addressはスペースで区切って組み合わせることもできます(AND条件)。

設定の検証:sshd -t と sshd -T

コマンド用途
sshd -t設定ファイルの構文エラーの有無だけをチェックする(問題なければ何も出力しない)
sshd -TMatchブロックなどをすべて評価した「最終的に有効になる設定値」を一覧表示する(意図通りの設定が反映されているかの確認に有用)
$ sudo sshd -t
# 構文エラーがなければ、この行のように何も表示されない

$ sudo sshd -t
/etc/ssh/sshd_config line 42: Bad configuration option: PermitRootLoginn
# typoがあれば、行番号付きでエラーが表示される

$ sudo sshd -T | grep -i permitrootlogin
permitrootlogin prohibit-password

この行のこの値に注目:sshd -Tの出力は、Matchブロックによる上書きも含めて実際に適用される値を表示するため、複雑な設定になるほど-tだけでなく-Tも併用して確認する価値があります。設定を反映させる前に、この2つを実行する習慣をつけておくことが重要です。

設定変更時に自分を締め出さないための鉄則

締め出しを防ぐ鉄則: sshd_configを変更してsystemctl restart sshdする前に、必ず既存のSSHセッションを開いたままにしておきます。設定ミスがあった場合、新規接続ができなくなっても、開いたままのセッションから修正・復旧できます。既存セッションを全て閉じた後に設定ミスへ気づくと、コンソールアクセス(VNC/シリアルコンソールなど)がない限り復旧できなくなる恐れがあります。この点はLPIC-2 212.3が明示的に問う実務上重要な項目です。

具体的には、次の手順を徹底します。

  1. 現在のセッションはそのまま開いたまま、別のターミナルで新しいSSH接続を用意しておく
  2. 設定ファイルを編集する前に、必ずバックアップを取る
  3. 編集後、sshd -tで構文エラーがないことを確認する
  4. 可能であればsystemctl restartではなくsystemctl reloadを使う。reloadは実行中のプロセスに設定の再読み込みシグナルを送るだけで、既存の接続を切断しない(新規接続だけが新しい設定で扱われる)ため、restartよりも安全
  5. 別ターミナルから新規接続を試み、成功することを確認してから、初めて元のセッションを閉じる
$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak   # 正常時は無出力
$ sudo vi /etc/ssh/sshd_config        # エディタが起動し、保存・終了すると通常のプロンプトに戻る
$ sudo sshd -t && echo "syntax OK"
syntax OK
$ sudo systemctl reload sshd         # 正常時は無出力
$ ssh -p 22 alice@server            # 別ターミナルから新規接続を試す
Last login: Fri Aug 21 08:55:12 2026 from 203.0.113.10
alice@server:~$

この行のこの値に注目: Last login: ...から始まるバナーとシェルプロンプトが表示されれば、新しい設定のままで新規接続が成功したことを意味します。ここで接続に失敗した場合は、まだ元のセッションが生きているうちにsshd_config.bakを戻してsystemctl reloadし直せます。

さらに安全性を高める運用として、atコマンドで一定時間後に設定を自動的に元へ戻すジョブを事前に仕込んでおく方法があります。新しい設定に問題があって締め出された場合でも、指定時間後に自動でロールバックされます。

$ echo "cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config && systemctl restart sshd" | sudo at now + 10 minutes
job 3 at Thu Aug 20 21:15:00 2026
$ atq                                 # 予約されているジョブの一覧を確認
3       Thu Aug 20 21:15:00 2026 a root

ここで確認すべきこと: 新しい設定での接続に問題がないことを確認できたら、予約したロールバックジョブが不要になるため、atrmでジョブ番号を指定して取り消します。取り消し忘れると、意図せず設定が元に戻ってしまいます。

$ sudo atrm 3                        # 動作確認が取れたので、予約していたロールバックジョブを取り消す(正常時は無出力)
$ atq                                 # ジョブが一覧から消えたことを確認

SSHによるポートフォワーディング

ポートフォワーディングには3つの方式があり、それぞれ通信の向きと用途が異なります。

ローカルフォワード(-L)

構成イメージ
[手元PC:8080] --SSH接続-- [bastion.example.com] --TCP-- [internal-db:5432]

$ ssh -L 8080:internal-db:5432 bastion.example.com
Last login: Fri Aug 21 09:40:02 2026 from 203.0.113.10
bastion:~$
# 手元の8080番ポートへの接続を、bastion経由でinternal-dbの5432番へ転送する
# 直接到達できない社内DBへ、踏み台サーバー越しにアクセスする典型例
# シェルプロンプトが表示されている間、ポートフォワードも並行して有効になる
$ psql -h localhost -p 8080 -U dbuser mydb    # 手元からはlocalhost:8080に接続するだけでよい
Password for user dbuser:
psql (16.4)
Type "help" for help.

mydb=>

この行のこの値に注目: psqlが正常にプロンプト(mydb=>)まで到達すれば、手元の8080番ポート宛の通信が、SSHトンネルを経由してinternal-db:5432まで正しく転送されていることが確認できます。手元のPCはinternal-dbというホスト名を解決できる必要すらなく、あくまでbastionから見た宛先として指定するだけで済みます。

書式は-L [bind:]port:host:hostportです。host:hostportは、SSH先のbastionから見た宛先(internal-db:5432)であり、手元PCから直接その宛先に到達できる必要はありません。

リモートフォワード(-R)

構成イメージ
[public-server:9000] --SSH接続-- [手元PC(NAT内):3000]

$ ssh -R 9000:localhost:3000 public-server.example.com
Last login: Fri Aug 21 09:45:11 2026 from 198.51.100.20
public-server:~$
# public-server側の9000番ポートへの接続を、SSH経由で手元のlocalhost:3000へ転送する
# NAT/ファイアウォールの内側にあるため外部から直接アクセスできないサービスを、
# 外部に公開されたサーバー経由で一時的に公開する例

この行のこの値に注目: 接続後、public-server上でcurl localhost:9000を実行すると、手元のlocalhost:3000で動いているサービスの応答がそのまま返ってきます。逆方向(外部→手元)に転送するのがリモートフォワードの特徴です。

書式は-R [bind:]port:host:hostportです。既定では、public-server側で開かれる9000番ポートはpublic-server自身のlocalhostからしか接続できません。他のホストからもこの転送ポートへ接続できるようにするには、public-server側のsshd_configでGatewayPorts yes(またはclientspecified)を設定しておく必要があります。

ダイナミックフォワード(-D)

$ ssh -D 1080 bastion.example.com
Last login: Fri Aug 21 09:50:33 2026 from 203.0.113.10
bastion:~$
# bastion.example.comを経由するSOCKSプロキシを、ローカルの1080番ポートに立てる
$ curl -x socks5h://localhost:1080 http://internal-service.local/
<!DOCTYPE html>
<html><body>Internal Service Dashboard</body></html>
# SOCKSプロキシ経由で、bastionから到達可能な内部サービスへアクセスする

この行のこの値に注目: internal-service.localは手元のPCからは名前解決すらできないホストですが、socks5h://(末尾のhがDNS解決もSOCKS経由で行うことを意味する)を指定することで、名前解決自体をbastion側に委任し、正しく応答を取得できています。socks5://(hなし)だと手元で名前解決しようとして失敗するため、内部限定のホスト名を扱う場合はh付きが必須です。

-D portで立てたポートはSOCKSプロキシとして動作し、宛先を1つに固定するローカル/リモートフォワードと異なり、任意の宛先への通信をまとめてトンネルできます。ブラウザやcurlなどのSOCKS対応クライアント側でプロキシ設定するだけで使えます。

いずれの方式でも、フォワード専用でシェルを使わない場合は-N(リモートでコマンドを実行しない)と-f(認証完了後にバックグラウンドへ移行する)を併用するのが定石です。

$ ssh -N -f -L 8080:internal-db:5432 bastion.example.com
# シェルを開かず、フォワードの確立後はバックグラウンドプロセスとして動き続ける(正常時は無出力でプロンプトに戻る)
$ ps aux | grep "[s]sh -N -f"
alice    6120  0.0  0.1  15200  4200 ?        S    09:52   0:00 ssh -N -f -L 8080:internal-db:5432 bastion.example.com

この行のこの値に注目: -fを付けると認証完了後すぐにシェルのプロンプトへ戻りますが、フォワードのプロセス自体はpsで確認できるようにバックグラウンドで動き続けています。このプロセスを終了させるにはkillでPID(この例では6120)を指定するか、対応するローカルポートへの接続をすべて閉じます。

① ローカルフォワード(-L) 手元PC localhost:8080 SSH暗号化トンネル bastion 平文TCP internal-db :5432 ② リモートフォワード(-R) public-server :9000(外部公開) SSH暗号化トンネル(逆方向) 手元PC(NAT内) localhost:3000 ※外部からアクセスできない手元PCのサービスを、public-server経由で一時的に公開する ③ ダイナミックフォワード(-D) 手元PC SOCKS :1080 SSH暗号化トンネル bastion 平文TCP(宛先は自由) internal-service.local 任意の宛先ホスト …
SSHポートフォワード3種類の通信経路。ローカルフォワード(-L)は手元の待受ポートからSSHトンネルを通ってbastionに至り、そこから平文TCPで内部サーバーへ届きます。リモートフォワード(-R)はこの向きが逆になり、外部公開サーバーからSSHトンネルを通じて手元PCのサービスに届きます。ダイナミックフォワード(-D)はSOCKSプロキシとして動作し、宛先を1つに固定せず任意の宛先へ転送できます。

X11フォワーディング

SSH接続の中でリモート側のGUIアプリケーションを起動し、その画面を手元に表示させる仕組みがX11フォワーディングです。

$ ssh -X alice@server     # 制限付き(untrusted)モードでX11フォワーディングを有効化
Last login: Fri Aug 21 09:55:20 2026 from 203.0.113.10
alice@server:~$ echo $DISPLAY
localhost:10.0
$ ssh -Y alice@server     # 信頼(trusted)モードでX11フォワーディングを有効化

この行のこの値に注目: ログイン後にecho $DISPLAYでlocalhost:10.0のような値が表示されれば、X11フォワーディングが有効になっていることが確認できます。この状態でxtermやxeyesのようなGUIアプリケーションを起動すると、その描画がSSHトンネル経由で手元の画面に転送されます。何も表示されない場合は、サーバー側のX11Forwardingがnoになっているか、クライアント側でX11がインストールされていない可能性があります。

-XはX11のSECURITY拡張による制限が適用される「信頼しない」モードで、リモート側のアプリケーションが手元のキーボード入力を奪う(キーロギング的な操作をする)ことなどを防ぎます。一部の古いアプリケーションはこの制限下では正しく動作しないことがあり、その場合に制限を外す-Y(信頼するモード)が使われますが、リモート側のアプリケーションに手元のX環境への強い操作権限を与えることになるため、信頼できるサーバーに対してのみ使うべきです。

接続後、リモート側ではDISPLAY環境変数がlocalhost:10.0のような値に自動設定されます。これは実際のディスプレイではなく、SSHが用意した仮想ディスプレイ番号で、リモートのXクライアントからの描画要求はこの番号を通じてSSHトンネル経由で手元に転送されます。xauthは、この仮想ディスプレイへの接続を許可された特定のクライアントだけに限定するための、ランダムな認証クッキー(マジッククッキー)を管理する仕組みです。sshd_config側のX11UseLocalhost(既定でyes)は、この転送用リスナーをサーバーのlocalhostだけにバインドするか、外部からも到達可能にするかを制御します。

~/.ssh/configの活用

接続先ごとに長いオプションを毎回入力する代わりに、クライアント側の~/.ssh/config(全ユーザー共通なら/etc/ssh/ssh_config)にホストごとの設定をまとめておくと便利です。

~/.ssh/config の実用例
Host bastion
    HostName bastion.example.com
    User ops
    Port 22
    IdentityFile ~/.ssh/id_ed25519_bastion
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host db-internal
    HostName 10.0.2.15
    User alice
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_db
    ProxyJump bastion
    ForwardAgent no
    StrictHostKeyChecking yes

Host *.example.com
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 10m

これによりssh db-internalだけで、指定したホスト名・ユーザー・ポート・鍵ファイルでの接続が行えます。主なディレクティブは次の通りです。

ディレクティブ意味
ProxyJump直接到達できない内部サーバーに、踏み台サーバーを経由して1コマンドで接続する(-J相当)。以前はこの用途にProxyCommandとncを組み合わせる方法が使われていたが、ProxyJumpの方が簡潔で、かつ踏み台側にエージェント転送を渡す必要がない
ControlMaster / ControlPath / ControlPersist1本目の接続でTCP接続と認証を確立した後、そのソケットを介して2本目以降のssh/scp/sftpを高速に多重化する「コネクション多重化」の設定。ControlPersist 10mは、最後のセッションが終わってもソケットを10分間維持し、再接続コストを省く
ServerAliveIntervalクライアントからサーバーへの生存確認間隔(sshd_configのClientAliveIntervalのクライアント版)
ForwardAgentssh-agentの転送を有効にするか(既定no)
StrictHostKeyCheckingホスト鍵の検証を厳格にするか。yes(未知のホストへの接続を拒否)/accept-new(未知のホストは自動登録するが、変化があれば拒否)/no(検証しない、危険なので非推奨)
実務での考え方: ControlMasterによる接続多重化は、同じホストへ短時間に何度もssh/scpを実行するスクリプトや、Ansibleのようなツールで特に効果を発揮します。2回目以降の接続でTCPハンドシェイクと認証プロセスを丸ごと省略できるため、体感速度が大きく向上します。ControlPathで指定するディレクトリ(例の~/.ssh/sockets/)は事前にmkdir -pで作成しておく必要があります。

ssh-agentとssh-add

パスフレーズ付きの秘密鍵を使う場合、接続のたびにパスフレーズを入力するのは煩雑です。ssh-agentはメモリ上に復号済みの鍵を保持しておくエージェントで、一度ssh-addで鍵を登録すれば、以降の接続ではパスフレーズの再入力が不要になります。

$ eval "$(ssh-agent)"
Agent pid 12345
# ssh-agentをバックグラウンドで起動し、SSH_AUTH_SOCK/SSH_AGENT_PIDを
# 現在のシェルの環境変数として反映する(以後のsshコマンドがこのエージェントを利用できるようになる)

$ ssh-add ~/.ssh/id_ed25519      # 鍵をエージェントに登録(パスフレーズを一度だけ入力)
Enter passphrase for /home/alice/.ssh/id_ed25519:
Identity added: /home/alice/.ssh/id_ed25519 (alice@laptop)

$ ssh-add -l                    # 登録済みの鍵の指紋一覧を表示
256 SHA256:AbCdEf... alice@laptop (ED25519)

$ ssh-add -L                    # 登録済みの鍵の公開鍵本体を表示(authorized_keysへコピーする際などに便利)
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... alice@laptop

$ ssh-add -t 3600 ~/.ssh/id_ed25519   # 1時間(3600秒)経過すると自動的にエージェントから失効する
Enter passphrase for /home/alice/.ssh/id_ed25519:
Identity added: /home/alice/.ssh/id_ed25519 (alice@laptop)
Lifetime set to 3600 seconds
$ ssh-add -D                    # エージェントに登録されている全ての鍵を削除
All identities removed.

この行のこの値に注目: Lifetime set to 3600 secondsが表示されれば、有効期限付きで鍵が登録されたことを意味し、指定秒数が経過するとssh-add -lの一覧から自動的に消えます(明示的なssh-add -Dを待たずに失効します)。All identities removed.は、エージェント上の鍵をすべて削除したことを示す確認メッセージです。

さらにssh -A(エージェントフォワーディング)を使うと、踏み台サーバーに自分の秘密鍵を置くことなく、手元のエージェントの鍵をそのまま使ってさらに奥のサーバーへ接続できます。ただしエージェントフォワーディングを有効にすると、その踏み台サーバー上でroot権限を持つ者(あるいは侵害した攻撃者)が、接続が張られている間、転送されたエージェントのソケットを使って手元の鍵で任意のサーバーへ認証を行えてしまいます。秘密鍵そのものは盗まれませんが、セッションが有効な間は「あなたの鍵として振る舞える」状態になるため、信頼できないサーバーでは有効化すべきではありません。

実務での考え方: 踏み台経由で多段接続したいだけであれば、エージェントフォワーディング(-A)ではなくProxyJumpを使う方が安全です。ProxyJumpでは、踏み台サーバーはあくまで通信を中継するだけで、最終的な公開鍵認証の暗号処理(署名の生成)は手元のクライアントとエージェントの間で完結し、踏み台サーバーにエージェントソケットへのアクセス権を渡す必要がありません。

scp/sftp/rsync over sshの使い分け

ツール特徴
scp単純なファイルコピーに手軽。ただし歴史的な実装(rcpベース)にパス検証まわりの問題が指摘されたことがあり、近年のOpenSSHでは内部的にSFTPプロトコルを使う実装に切り替わっている。新規のスクリプトでは非推奨傾向にあり、公式にも縮小方向(保守モード)とされている
sftp対話的なファイル転送セッション。パスの扱いがscpより安全で、ディレクトリの一覧表示・再開・部分取得などの操作性にも優れる
rsync -e ssh差分転送(変更のあった部分だけを送る)に対応し、大量のファイルや繰り返しの同期に適する。転送の中断・再開にも強い
$ scp report.pdf alice@server:/tmp/                       # 単発の簡単なコピー
report.pdf                                   100%  240KB   1.8MB/s   00:00
$ sftp alice@server                                        # 対話的な転送セッション
Connected to server.
sftp> put report.pdf /tmp/
Uploading report.pdf to /tmp/report.pdf
report.pdf                                   100%  240KB   1.9MB/s   00:00
sftp> exit
$ rsync -avz -e ssh /local/data/ alice@server:/remote/data/  # 差分同期、繰り返し実行を想定
sending incremental file list
data/
data/report.pdf
data/logs/app.log

sent 245,891 bytes  received 148 bytes  49,208.00 bytes/sec
total size is 245,600  speedup is 1.00

この行のこの値に注目: scp・sftpの進捗表示にある転送速度(1.8MB/sなど)と所要時間から、転送が正常に完了したことが分かります。rsyncのsending incremental file list以下には、実際に転送された(差分のあった)ファイルだけが列挙され、変更のないファイルは一覧に出てきません。2回目以降の実行で表示されるファイル数が少なければ、差分転送が効いている証拠です。

実務では、1回限りの簡単なコピーにはscpやsftpで十分ですが、定期的なバックアップや大量ファイルの同期には、転送量を抑えられるrsyncが選ばれることが多くなっています。

ログの読み方とトラブルシューティング

$ sudo journalctl -u sshd --since "10 min ago"
Aug 20 21:03:11 server sshd[2201]: Accepted publickey for alice from 203.0.113.10 port 51422 ssh2: ED25519 SHA256:AbCdEf...
Aug 20 21:04:02 server sshd[2210]: Failed password for invalid user admin from 198.51.100.5 port 40011 ssh2
Aug 20 21:04:05 server sshd[2210]: Disconnecting invalid user admin 198.51.100.5 port 40011: Too many authentication failures

この行のこの値に注目:Accepted publickeyは認証成功、Failed passwordは認証失敗を表します。invalid userは、そもそもサーバー上に存在しないユーザー名でのログイン試行(総当たり攻撃でよく見られるパターン)です。Too many authentication failuresはMaxAuthTriesの上限に達したことによる強制切断です。

より詳細な監査を行いたい場合、LogLevel VERBOSEを設定すると、認証に使われた鍵のフィンガープリントまでログに記録されるようになります。どのユーザーが、どの具体的な鍵を使ってログインしたかを後から追跡したい場合に有用です。

/etc/ssh/sshd_config の該当行
LogLevel VERBOSE
$ ssh -v user@server              # クライアント側から接続過程を確認。-vv、-vvvでさらに詳細化
OpenSSH_9.6p1 Ubuntu-3ubuntu13.5, OpenSSL 3.0.13 30 Jan 2024
debug1: Connecting to server [203.0.113.10] port 22.
debug1: Connection established.
debug1: identity file /home/alice/.ssh/id_ed25519 type 3
debug1: Server host key: ssh-ed25519 SHA256:AbCdEf...
debug1: Host 'server' is known and matches the ED25519 host key.
debug1: Authentications that can continue: publickey,password
debug1: Next authentication method: publickey
debug1: Offering public key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:AbCdEf...
debug1: Server accepts key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:AbCdEf...
debug1: Authentication succeeded (publickey).

この行のこの値に注目: Authentications that can continue: publickey,passwordで、サーバー側が許可している認証方式の一覧を確認できます。Offering public key: ...とServer accepts key: ...が対になって表示されれば、その鍵で認証が試行され受理されたことを意味し、最終行のAuthentication succeeded (publickey)で認証方式が確定します。接続がうまくいかない場合、この一連の流れのどこで止まっているかを見ることで、鍵の不一致・認証方式の不許可などの原因を切り分けられます。

接続できない場合の主な確認ポイントは、サーバー側のsshdが起動しているか、ファイアウォールで対象ポートが許可されているか(ファイアウォールの章を参照)、~/.sshやauthorized_keysのパーミッションが適切か、そして~/.ssh/known_hostsにホストキーの不一致が記録されていないかです。

実務的なハードニング設定の完成形

ここまでの内容を踏まえた、実務で使えるレベルのsshd_configの完成例を示します。

/etc/ssh/sshd_config(ハードニング済みの完成形)
Port 22                                   # 待ち受けポート
AddressFamily any                         # IPv4/IPv6の両方で待ち受け

PermitRootLogin no                        # rootの直接ログインを全面禁止
PubkeyAuthentication yes                  # 公開鍵認証を有効化
PasswordAuthentication no                 # パスワード認証を無効化(鍵認証のみ許可)
PermitEmptyPasswords no                   # 空パスワードを禁止
KbdInteractiveAuthentication no           # キーボードインタラクティブ認証も無効化

MaxAuthTries 3                            # 認証失敗の上限を3回に制限
LoginGraceTime 30                         # 認証完了までの猶予を30秒に短縮
MaxStartups 10:30:60                      # 未認証接続の同時受付を制限(DoS対策)

AllowGroups sshusers                      # sshusersグループのメンバーのみログイン許可

ClientAliveInterval 60                    # 60秒ごとに生存確認
ClientAliveCountMax 3                     # 応答なしが3回続いたら切断

X11Forwarding no                          # 通常サーバーでは不要なため無効化
AllowTcpForwarding yes                    # 運用上必要な踏み台用途のため許可
GatewayPorts no                           # リモートフォワードの外部公開は許可しない
PermitTunnel no                           # tunデバイスによるVPN的トンネリングは禁止

LogLevel VERBOSE                          # 鍵のフィンガープリントまで記録

Subsystem sftp internal-sftp

Match Group sftponly                      # SFTP専用グループはchrootで隔離
    ChrootDirectory /srv/sftp/%u
    ForceCommand internal-sftp
    AllowTcpForwarding no
    X11Forwarding no

この設定を反映する際も、必ず前々章までのブート・復旧の考え方と同様、既存セッションを維持したまま段階的に検証し、sshd -t・sshd -Tでの確認を経てからsystemctl reload sshdすることを徹底してください。

確認クイズ

Q1. SSHプロトコルのうち、鍵交換とサーバーからクライアントへの通信の暗号化を担う層はどれですか?

解説: トランスポート層は、サーバーとクライアント間で鍵交換を行い、以後の通信を暗号化する役割を持ちます。ホスト鍵によるサーバー認証もこの層で行われます。

Q2. 以前記録したものと異なるホスト鍵がサーバーから返された際に表示される警告への対処として、最も適切なものはどれですか?

解説: この警告は中間者攻撃の可能性を示すものです。サーバー再構築など正当な理由があるか確認せずにエントリを削除すると、実際の攻撃を見逃すおそれがあります。

Q3. known_hostsから特定ホストの古いエントリだけを安全に削除するために使うコマンドはどれですか?

解説: ssh-keygen -Rは、指定したホストのエントリだけをknown_hostsから安全に削除します。ssh-keyscanはホスト鍵の事前収集、ssh-add -Dはエージェントの鍵を全削除するコマンドです。

Q4. 公開鍵認証において、秘密鍵はどのように扱われますか?

解説: 公開鍵認証では、クライアントが手元の秘密鍵でセッション固有のデータに署名し、サーバーは登録済みの公開鍵でその署名を検証します。秘密鍵自体がネットワークを流れることはありません。

Q5. 現在のOpenSSHで、鍵長が短く高速で、特別な互換性要件がなければ第一候補とされる鍵の種類はどれですか?

解説: Ed25519は固定256bit相当の鍵長で最も高速かつ署名サイズも小さく、現在の事実上の標準的な選択肢です。

Q6. authorized_keysの鍵ごとのオプションのうち、ポートフォワード・X11転送・エージェント転送などリスクのある機能をまとめて一括禁止するものはどれですか?

解説: restrictオプションは、ポートフォワード・X11転送・エージェント転送・pty割り当てなど、リスクのある機能をまとめて禁止し、新しい機能が将来追加されても安全側に倒れる設計になっています。

Q7. sshd_configのPermitRootLoginで、パスワード認証は禁止しつつ公開鍵認証でのrootログインのみ許可する値はどれですか?

解説: prohibit-passwordは、パスワード認証を禁止しながら公開鍵認証でのrootログインは許可する設定です。noはrootログインを完全に禁止します。

Q8. AllowUsers・DenyUsers・AllowGroups・DenyGroupsを併用した場合の評価順序として正しいものはどれですか?

解説: sshdはDenyUsers → AllowUsers → DenyGroups → AllowGroupsの順に評価します。Denyのいずれかに一致すればその時点で拒否が確定します。

Q9. ChrootDirectoryで指定するディレクトリに必要な条件はどれですか?

解説: ChrootDirectoryで指定するディレクトリとその全ての親ディレクトリは、所有者がrootで他ユーザーが書き込めない状態でなければならず、満たさない場合sshdは接続を拒否します。

Q10. 設定変更後にsshdを再起動する際、既存の接続を切断せずに新しい設定を反映できるコマンドはどれですか?

解説: reloadは実行中のプロセスに設定の再読み込みシグナルを送るだけで、既存の接続を切断しません。restartはプロセスを再起動するため、既存接続を含めて切断される可能性があります。

Q11. リモートの特定ポートへの接続をSSH経由でローカル側の宛先へ転送するオプションはどれですか?

解説: -R(リモートフォワード)は、リモート側の特定ポートへの接続を、SSH経由でローカル側の宛先へ転送します。-Lはその逆(ローカルフォワード)、-Dは任意の宛先へ転送するSOCKSプロキシです。

Q12. リモートフォワード(-R)で開いたポートに、サーバーのlocalhost以外の外部ホストからも接続できるようにするために必要な設定はどれですか?

解説: 既定ではリモートフォワードで開いたポートはサーバーのlocalhostからしか接続できません。外部ホストからも接続可能にするにはGatewayPorts yes(またはclientspecified)が必要です。

Q13. 踏み台サーバーを経由して多段接続する際、エージェントフォワーディング(-A)よりProxyJumpの方が安全とされる理由はどれですか?

解説: ProxyJumpでは踏み台サーバーは通信を中継するだけで、公開鍵認証の署名処理は手元のクライアントとエージェントの間で完結します。エージェントフォワーディングでは踏み台サーバーが一時的にエージェントを利用できてしまうリスクがあります。

Q14. 次のjournalctlの出力のうち、総当たり攻撃でよく見られる、存在しないユーザー名でのログイン試行を示す行はどれですか?
Failed password for invalid user admin from 198.51.100.5 port 40011 ssh2

解説: invalid userは、そもそもサーバー上に存在しないユーザー名でのログイン試行を表し、総当たり攻撃でよく見られるパターンです。