- Published on
SSH運用完全ガイド: 鍵の管理からロックアウト事故のないサーバーハードニングまで
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- 1. 鍵の作成 — どの種類を選ぶか
- 2. 鍵の配布と回収 — authorized_keys の扱い方
- 3. クライアント設定 — ~/.ssh/config が標準ドキュメント
- 4. ホスト鍵の検証 — 警告を無視しない
- 5. sshd のハードニング — ロックアウトしない順序で
- 6. ポートフォワーディングとトンネル
- 7. 接続できないとき — レイヤー別の診断順序
- 8. 監査と運用の慣行
- クイズ: 理解度を確認しましょう
- おわりに
- 参考資料
- 関連記事
はじめに
SSH は学ぶのは簡単で、運用するのは難しい技術です。接続さえできればよかった時代には ssh user@host で十分でしたが、サーバーが数十台になり監査要件が付いた瞬間に問いが変わります。鍵は誰が管理するのか、退職者のアクセスはどう切るのか、踏み台を通過する経路はどう標準化するのか、そして 設定を間違えて自分自身をロックアウトしたらどうするのか。
このブログにはすでに SSH プロトコル徹底解説 があります。その記事はトランスポート層、認証方式、チャネル、証明書、Terrapin 攻撃といったプロトコル内部を扱います。この記事は正反対の方向です。プロトコルを知らなくてもよいが、サーバー100台を安全に運用しなければならない人のための設定と手順がテーマです。
基準バージョンは OpenSSH 9.x 以上です。オプションの可用性はバージョンによって異なるため、この記事で挙げたディレクティブが動作しない場合は、まずサーバーにインストールされているバージョンを確認してください。
ssh -V
sshd -V
1. 鍵の作成 — どの種類を選ぶか
現在の推奨は二つです。Ed25519 が基本の選択であり、FIPS 準拠など組織のポリシーが要求する場合にのみ RSA 4096 を使います。
ssh-keygen -t ed25519 -C 'youngju@laptop-2026' -f ~/.ssh/id_ed25519
ssh-keygen -t rsa -b 4096 -C 'youngju@laptop-2026' -f ~/.ssh/id_rsa
ssh-keygen -t ecdsa-sk -f ~/.ssh/id_ecdsa_sk
ssh-keygen -t ed25519-sk -f ~/.ssh/id_ed25519_sk
-tは鍵の種類、-bはビット数(RSA にのみ意味があります)、-Cはコメント、-fは出力ファイルです。-skの接尾辞が付いた種類は FIDO2 ハードウェアセキュリティキー を要求します。秘密鍵そのものがハードウェアの外に出ないため、漏洩リスクが大きく下がります。OpenSSH 8.2 以上でサポートされます。
コメントに人と機器を明示する習慣が重要です。authorized_keys に20行たまったとき、どの行が誰のものかを知る唯一の手がかりになります。
鍵には必ずパスフレーズを設定します。自動化の都合で難しい場合はエージェントを使います。
ssh-keygen -p -f ~/.ssh/id_ed25519
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
ssh-add -l
ssh-add -D
ssh-add -D はエージェントに登録した鍵をすべて削除します。共用ワークステーションを離れるときの習慣として実行してください。
指紋の確認は鍵の配布と監査で使います。
ssh-keygen -lf ~/.ssh/id_ed25519.pub
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
2. 鍵の配布と回収 — authorized_keys の扱い方
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@10.0.3.14
ssh-copy-id は便利ですが、サーバー台数が増えると管理方式としては不適切です。構成管理ツール(Ansible、Salt など)や SSH 証明書へ移行する必要があります。ファイルを直接管理するなら権限が正確でなければなりません。権限が緩いと sshd は静かに鍵を拒否します。これが「鍵を入れたのにパスワードを聞かれる」の最も多い原因です。
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
ホームディレクトリ自体がグループ書き込み可能でも拒否されます。
chmod 755 ~
authorized_keys の各行には制約をかけられます。デプロイ用の鍵のように用途が決まっている鍵に特に有効です。
restrict,from="10.0.0.0/8",command="/usr/local/bin/deploy-only" ssh-ed25519 AAAAC3Nza... deploy@ci
restrictはすべての機能を切る安全なデフォルトです。その後に必要なものだけを再度有効にします。from=は接続元を制限します。command=はクライアントが何を要求しても指定したコマンドだけを実行します。
回収はファイルから行を消して終わりではありません。すでに開いているセッションはそのまま生きています。完全な回収は次の順序です。
sudo -u deploy sed -i '/deploy@ci/d' /home/deploy/.ssh/authorized_keys
who
sudo pkill -TERM -u deploy sshd
破壊的コマンドの警告: 最後のコマンドは該当ユーザーのすべての SSH セッションを切断します。自分のセッションが含まれる可能性があるため、ユーザーと対象アカウントを必ず確認してください。
3. クライアント設定 — ~/.ssh/config が標準ドキュメント
接続方法を人の記憶ではなくファイルに書いておくと、チーム全体のミスが減ります。
Host bastion
HostName bastion.example.com
User youngju
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 3
Host prod-*
User deploy
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519_deploy
IdentitiesOnly yes
StrictHostKeyChecking yes
Host prod-web-01
HostName 10.0.3.14
主要なディレクティブの意味は次のとおりです。
ProxyJump(-J)は踏み台を経由します。OpenSSH 7.3 以上で使え、以前使われていたProxyCommandの組み合わせよりはるかに安全で簡潔です。IdentitiesOnly yesは 指定した鍵だけを試す ようにします。これがないとエージェントに登録されたすべての鍵を順に試し、サーバーのMaxAuthTries(既定6)に引っかかって認証が失敗します。鍵を複数持つ人が必ず遭遇する代表的な問題です。ServerAliveIntervalとServerAliveCountMaxは、アイドル接続が NAT やファイアウォールによって静かに切られるのを防ぎます。
コマンドラインでも同じことができます。
ssh -J bastion deploy@10.0.3.14
ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519_deploy deploy@10.0.3.14
接続の再利用は繰り返しの接続を大きく高速化します。
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 10m
ControlPersist は最後のセッション終了後もマスター接続を維持する時間です。ただし 共用ワークステーションでは他のユーザーがソケットを再利用する危険 があるため、ソケットパスの権限を確認してください。
4. ホスト鍵の検証 — 警告を無視しない
ホスト鍵の警告は中間者攻撃に対する唯一の防衛線です。ところがサーバーを再インストールするたびに出るため、習慣的に無視するようになります。その習慣が危険です。
ssh-keygen -F 10.0.3.14
ssh-keygen -R 10.0.3.14
ssh-keyscan -t ed25519 10.0.3.14
-Fは known_hosts から項目を探し、-Rは削除します。ssh-keyscanはサーバーのホスト鍵を取得します。取得した値を検証せずに信頼しては意味がありません。サーバーのコンソールで指紋を確認して照合する必要があります。
規模が大きくなったら SSH 証明書へ移行するのが正解です。ホスト鍵を CA で署名しておけば、クライアントは CA ひとつだけを信頼すればよくなります。
@cert-authority *.example.com ssh-ed25519 AAAAC3Nza...
ユーザー鍵も同じ方式で署名できます。このとき authorized_keys をサーバーごとに管理する必要がなくなり、有効期間を短くすることで回収の問題を構造的に解決できます。証明書の発行手順と主要オプションは組織の CA 実装によって異なるため、ssh-keygen の証明書関連オプションはインストール済みバージョンの man ページで確認してください。
5. sshd のハードニング — ロックアウトしない順序で
サーバー設定を変更するときに 最も重要なのは順序です。間違えると自分自身をロックアウトします。
安全な手順は次のとおりです。
- 現在のセッションを維持したまま 新しいターミナルをもう一つ開いておきます。
- 設定ファイルを修正します。
sshd -tで文法を検査します。- サービスをリロードします。
- 既存のセッションを閉じずに 新しいターミナルから接続を確認します。
- 確認が終わったあとにのみ既存のセッションを閉じます。
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak-2026-08-15
sudo vi /etc/ssh/sshd_config
sudo sshd -t
sudo systemctl reload sshd
sshd -t は文法だけを検査します。実際に適用される値を確認するには拡張テストモードを使います。
sudo sshd -T | sort | head -40
sudo sshd -T -C user=deploy,host=10.0.3.14,addr=10.0.3.14 | grep -i -E 'passwordauth|pubkey|permitroot'
-T は有効な設定全体を、-C は特定の接続条件における Match ブロックの適用結果を表示します。Match ブロックを使うなら、このコマンドで必ず検証してください。条件が想定と違ってマッチすることは頻繁にあります。
推奨設定の例です。各ディレクティブの既定値は man ページに基づきます。
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
AllowGroups sshusers
X11Forwarding no
PermitEmptyPasswords no
LogLevel VERBOSE
各値の意味を押さえます。
PermitRootLoginの既定値はprohibit-passwordです。つまり既定状態でも root のパスワードログインは塞がれており、鍵ログインは許可されています。完全に塞ぐにはnoに設定します。PasswordAuthenticationの既定値はyesです。鍵認証へ移行したなら 必ず明示的にnoへ変更する必要があります。KbdInteractiveAuthenticationの既定値もyesです。パスワードを塞いだつもりでこの経路から入られる場合があるので、あわせて切ってください。旧名のChallengeResponseAuthenticationは廃止された別名です。MaxAuthTriesの既定値は6であり、失敗がこの値の半分に達した時点からログに記録されます。LoginGraceTimeの既定値は120秒です。ログインできていない接続を長く保持しないよう短くするのが望ましいです。AllowGroupsを使うと指定したグループのユーザーだけがログインできます。DenyGroupsが先に処理され、その次にAllowGroupsが処理されます。LogLevel VERBOSEは認証に使われた鍵の指紋をログに残します。監査要件がある環境では事実上必須です。
Match ブロックで例外を作ります。
Match Group sftponly
ChrootDirectory /srv/sftp/%u
ForceCommand internal-sftp
AllowTcpForwarding no
PermitTunnel no
Match は条件が合えば次の Match またはファイル末尾までの設定を上書きします。同じキーワードが複数の Match で満たされる場合、最初のものだけが適用されます。順序が結果を変えるので注意してください。
ディストリビューションによって設定が分割されています。最近の Debian/Ubuntu と RHEL 系は Include /etc/ssh/sshd_config.d/*.conf の形で断片ファイルを読み込みます。Include されたファイルが前方で値を先に定義すると、下側の設定が無視されることがあるため、変更前に sshd -T で実際の適用値を確認する習慣が重要です。
6. ポートフォワーディングとトンネル
ssh -L 15432:db.internal:5432 bastion
ssh -R 8080:localhost:3000 relay.example.com
ssh -D 1080 bastion
ssh -N -f -L 15432:db.internal:5432 bastion
-Lは ローカルフォワーディング です。自分のマシンの15432に入ってきた接続を SSH サーバー経由でdb.internal:5432へ送ります。内部ネットワークの DB につなぐときに使います。-Rは リモートフォワーディング です。サーバーのポートを自分のマシンに引き寄せます。既定ではサーバーのループバックにのみバインドされ、外部に開くにはサーバー側でGatewayPortsを有効にする必要があります。-Dは 動的フォワーディング で SOCKS プロキシを作ります。-Nはリモートコマンドを実行せず、-fはバックグラウンドへ送ります。トンネル専用の接続で一緒に使います。
フォワーディングは強力なぶん統制が必要です。サーバー側で次のディレクティブによって制限します。
AllowTcpForwarding no
AllowAgentForwarding no
GatewayPorts no
PermitOpen 10.0.5.20:5432
エージェントフォワーディング(-A)は特に慎重に扱う必要があります。経由サーバーの root 権限を持つ人は、あなたのエージェントソケットを通じてあなたの鍵で別のサーバーに認証できます。踏み台の経由が目的なら ProxyJump を使ってください。エージェントを露出させずに同じ結果が得られます。
PermitOpen はフォワーディングを完全に塞がずに宛先をホワイトリストで制限します。開発者が本番 DB にはつなぐ必要があるが、他の内部サービスにはアクセスさせたくない状況に適しています。複数の宛先を空白区切りで列挙でき、none を指定するとすべてのフォワーディング要求を拒否します。
ファイル転送は別のツールではなく同じチャネルを使います。最近の OpenSSH の scp は内部的に SFTP プロトコルを使うよう変更されたため、以前は通っていた一部のパス展開の挙動が変わりました。大量の同期であれば rsync を SSH の上で走らせるほうが、再開と部分転送の面で有利です。
scp -i ~/.ssh/id_ed25519 ./app.tar.gz deploy@10.0.3.14:/tmp/
sftp -J bastion deploy@10.0.3.14
rsync -avz -e 'ssh -J bastion' ./dist/ deploy@10.0.3.14:/srv/app/
7. 接続できないとき — レイヤー別の診断順序
問題をレイヤーに分けると原因が速く絞り込めます。
ステップ1 — ネットワークは届いているか。
nc -vz 10.0.3.14 22
ss -tlnp | grep ':22'
ステップ2 — サーバーは何を提示しているか。
ssh -vvv deploy@10.0.3.14 2>&1 | head -60
-v は一段階、-vv、-vvv と進むほど詳しくなります。読み方のコツは次のとおりです。
debug1: Connecting to ...まで到達しなければネットワークかファイアウォールの問題です。debug1: Offering public key: ...のあとにAuthentications that can continueが繰り返されるなら、サーバーがその鍵を拒否したということです。Permission denied (publickey)は認証失敗、Connection refusedはデーモン未起動、Connection timed outは経路遮断です。この三つのメッセージの区別が診断の半分です。
ステップ3 — サーバー側のログを見る。
sudo journalctl -u sshd -n 100 --no-pager
sudo journalctl -u sshd --since '10 min ago' -g 'Failed|Invalid|Accepted'
RHEL 系のユニット名は sshd、Debian/Ubuntu 系は ssh である場合が多いです。システムに合う名前を使ってください。
ステップ4 — 権限と SELinux。
sudo ls -ld /home/deploy /home/deploy/.ssh
sudo ls -l /home/deploy/.ssh/authorized_keys
sudo ausearch -m avc -ts recent | tail -20
sudo restorecon -Rv /home/deploy/.ssh
RHEL 系でホームディレクトリを手動で作った場合や、ファイルを別のパスからコピーした場合は、SELinux コンテキストが誤っていて鍵が拒否されます。restorecon が標準的な解法です。この症状はログに権限問題として明確に出ないことが多く、時間を大きく浪費します。
ステップ5 — アカウントの状態。
sudo passwd -S deploy
sudo chage -l deploy
getent group sshusers
パスワードがロックされたアカウントは、ディストリビューションの設定によっては鍵認証まで塞がれることがあります。期限切れのアカウントも同じ結果になります。
8. 監査と運用の慣行
誰がいつ入ってきたかを追跡できなければなりません。
last -a | head -20
lastb -a | head -20
sudo journalctl -u sshd -g 'Accepted' --since '7 days ago' | tail -40
LogLevel VERBOSE を有効にしておくと、認証に使われた鍵の指紋も一緒に記録されるため、この接続はどの鍵で行われたのか を事後に確認できます。この一行があるかないかが、事故調査で大きな差を生みます。
総当たり攻撃への対応は三方向です。まずパスワード認証を切れば、自動化された攻撃のほとんどは無意味になります。次にアクセスそのものを接続元で制限します。三つめは失敗回数の閾値に基づく遮断ツールを使うことです。ポートを22から変えるのはログのノイズを減らすだけであってセキュリティ対策ではない、という点は明確にしておくのがよいです。
定期点検の項目をリストにしておくと見落としません。
- 各サーバーの
authorized_keysにある鍵が現職者・現行システムのものかを四半期ごとに確認 - ホスト鍵の指紋リストを別のリポジトリに保管
sshd -Tの出力を構成管理で固定し変更を追跡- 踏み台のログを中央収集の対象に含める
- ハードウェアセキュリティキーまたは SSH 証明書への移行計画
サーバー台数が増えると人の規律では維持できません。次の段階へ進む時点を判断する基準を決めておくとよいです。サーバーが10台を超えたら authorized_keys を構成管理で配布し、人が20名を超えたら SSH 証明書へ移行し、監査要件が生じたら踏み台を単一の入口として強制しセッション記録を残します。この三段階を先送りすると後でまとめてやることになり、そのときにはどのサーバーにどの鍵があるのか誰も分からない状態になっています。
ファイアウォールで SSH アクセスを制限する方法は、このシリーズの ファイアウォールとアクセス制御ガイド で扱います。特にリモートからルールを変更している最中にロックアウトする事故を防ぐ手順を、あわせて読んでおいてください。
クイズ: 理解度を確認しましょう
クイズ1: 公開鍵を authorized_keys に入れたのにパスワードを聞かれ続けます。最初に確認するものは?
答え: ホームディレクトリと .ssh ディレクトリ、authorized_keys ファイルの権限です
解説: sshd は権限が緩いと鍵を静かに無視します。ホームディレクトリがグループ書き込み可能でも拒否されます。
chmod 755 ~
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
それでも直らなければ、サーバーのログとクライアントの詳細ログを一緒に見ます。
ssh -vvv deploy@10.0.3.14 2>&1 | grep -i -E 'offering|authentications that can continue'
sudo journalctl -u sshd -n 50 --no-pager
RHEL 系なら SELinux コンテキストも確認してください。restorecon -Rv ~/.ssh で解決する場合が多いです。
クイズ2: 鍵が複数あり、特定のサーバーでだけ "Too many authentication failures" が出ます。原因と解決は?
答え: エージェントの鍵を順にすべて試してサーバーの MaxAuthTries に引っかかったのです。IdentitiesOnly で解決します
解説: MaxAuthTries の既定値は6です。エージェントに鍵が10個登録されていると、目的の鍵に到達する前に接続が切れます。
ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519_deploy deploy@10.0.3.14
恒久的にはクライアント設定に入れます。
Host prod-*
IdentityFile ~/.ssh/id_ed25519_deploy
IdentitiesOnly yes
クイズ3: sshd_config をリモートから修正しなければなりません。自分をロックアウトしないためにどの順序で作業しますか?
答え: 既存セッションを維持したまま文法検査後にリロードし、新しいセッションで接続を確認したあとにのみ既存セッションを閉じます
解説: リロードはすでに接続されたセッションを切りません。だから既存セッションが安全網の役割を果たします。
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak-2026-08-15
sudo sshd -t
sudo systemctl reload sshd
sudo sshd -T | grep -i -E 'permitrootlogin|passwordauthentication|allowgroups'
そして 別のターミナルから 接続を確認します。失敗したら、生きている既存セッションからバックアップを戻します。クラウドならシリアルコンソールへのアクセス手段をあらかじめ確保しておくことも重要です。
クイズ4: 踏み台を経由して内部サーバーに接続する必要があります。エージェントフォワーディングと ProxyJump のどちらを使い、それはなぜですか?
答え: ProxyJump を使います。エージェントフォワーディングは踏み台の管理者に鍵の使用権限を露出させます
解説: エージェントフォワーディング(-A)を使うと踏み台にエージェントソケットが露出します。そのサーバーの root 権限を持つ人は、ソケットを通じてあなたの鍵で別のサーバーに認証できます。ProxyJump は踏み台を単なる通り道としてのみ使い、認証は端から端まで行われます。
ssh -J bastion deploy@10.0.3.14
Host prod-*
ProxyJump bastion
クイズ5: 退職者のアクセスを遮断しました。authorized_keys から鍵を消しましたが、それで十分でしょうか?
答え: 十分ではありません。すでに開いているセッションはそのまま維持されます
解説: 鍵の削除は新しい認証を防ぐだけです。進行中のセッションと、そのセッションが作ったフォワーディングトンネルは生きています。
who
sudo pkill -TERM -u leaver sshd
sudo passwd -l leaver
またその人がデプロイ自動化や別のサーバーに自分の鍵を入れていた可能性があるため、全サーバーを洗う必要があります。こうした理由から、SSH 証明書と短い有効期間へ移行すると回収の問題が構造的に解決します。
クイズ6: 接続試行における "Connection timed out" と "Connection refused" はそれぞれ何を意味しますか?
答え: timed out はパケットが宛先に到達しなかったこと(経路・ファイアウォール)、refused は到達したがそのポートで待ち受けているサービスがないことです
解説: この区別が診断時間を大きく短縮します。
nc -vz 10.0.3.14 22
ss -tlnp | grep ':22'
sudo systemctl status sshd
refused ならサーバーまでは到達しているので、ファイアウォールではなくデーモンの状態やバインドアドレスを見ます。timed out ならセキュリティグループ、ルーティング、ホストのファイアウォールの順に確認します。
おわりに
SSH 運用における事故は、ほとんど二つの形でやってきます。アクセスが必要な人が入れないか、遮断されるべきだった人がまだ入れるかのどちらかです。前者の問題は騒がしく表面化し、後者の問題は静かに残ります。だからこそ監査と回収の手順が接続の利便性より優先されます。
いますぐ適用すべきことを三つに絞るとこうなります。パスワード認証とキーボードインタラクティブ認証を切り、LogLevel VERBOSE で鍵の指紋を残し、設定変更は必ず既存セッションを生かしたまま新しいセッションで検証してください。この三つだけでほとんどの事故を防げます。
参考資料
- sshd_config(5) — OpenSSH マニュアル (2026-08-15 確認)
- ssh_config(5) — OpenSSH マニュアル (2026-08-15 確認)
- ssh(1) — OpenSSH マニュアル (2026-08-15 確認)
- ssh-keygen(1) — OpenSSH マニュアル (2026-08-15 確認)
- OpenSSH 公式サイト (2026-08-15 確認)
関連記事
- 前の記事: プロセスとシグナル完全ガイド
- 次の記事: Git 取り消し完全ガイド
- SSH プロトコル徹底解説 — トランスポート層と証明書の内部動作
- SSH エスケープシーケンスとリモート運用 — セッションが固まったときの抜け方
- Linux ターミナル — コマンドの練習
- chmod 計算機 — 鍵ファイルの権限確認