SSHが勝手に切れる - ServerAlive とタイムアウト対策
SSH が勝手に切れるのはなぜか?
結論: 多くは無通信(idle)が続いた接続を、間にある NAT・ファイアウォールや sshd が黙って切るのが原因。少し放置すると
Broken pipeで落ちるなら、定期的に小さなパケットを流す keepalive が効いていない。client 側のServerAliveIntervalか server 側のClientAliveIntervalを設定すれば大半は止まる。
SSH 接続が「操作している間は平気なのに、少し席を外すと切れている」なら、ほぼ idle タイムアウトが犯人。TCP コネクションは無通信でもしばらくは生きているが、経路上の NAT 機器・ステートフルファイアウォール・ロードバランサは、一定時間パケットが流れないセッションを接続テーブルから消す。消された後にキー入力すると、行き場を失ったパケットが返り、次のようなメッセージで落ちる。
client_loop: send disconnect: Broken pipe
Write failed: Broken pipe packet_write_wait: Connection to 192.0.2.10 port 22: Broken pipe
Timeout, server not responding.
逆に「操作中なのに突然切れる」「数時間ぴったりで切れる」場合は、サーバ側の明示的なセッション上限や不安定な回線が疑わしい(後述)。まずは「idle で切れるのか、操作中でも切れるのか」を切り分けるのが最短。
前提(対象環境)
- client / server とも Ubuntu / 一般的な Linux(OpenSSH)
- 編集対象: client は
~/.ssh/config、server は/etc/ssh/sshd_config - server 側設定の反映には
sudoと sshd の再読み込みが必要
idle で切れるのか操作中でも切れるのかをどう見分けるか?
結論: 何もせず放置して切れるなら idle タイムアウト(NAT / firewall /
ClientAliveInterval)。top等を流して画面を更新し続けても切れるなら回線品質か明示的なセッション上限。前者は keepalive で直り、後者は別アプローチになる。
原因を二分するために、まず「通信を流し続けたら切れないか」を試す。
# 何も出力しないが接続は維持される簡易テスト(一定間隔で時刻を表示) $ ssh user@server 'while true; do date; sleep 30; done'
- これで切れなくなる → 無通信が原因の idle タイムアウト。keepalive で解決する(次節)
- これでも切れる → 回線断・無線の不安定、またはサーバ側の固定タイムアウト(
sshd_configの他設定や PAM、ロードバランサのハードリミット)を疑う
ssh -v で接続を張り、切れた瞬間のログを見るとさらに確実。
$ ssh -v user@server
debug1: client_loop: send disconnect: Broken pipe
切断の直前に keepalive の送受信ログ(debug1: Sending command ではなく channel のやり取り)が止まっているか、server not responding が出ているかで、どちら側が先に諦めたかが読める。
切れるまでの時間が毎回ほぼ一定なら、人為的なタイムアウト設定(NAT の conntrack やサーバの ClientAliveInterval × ClientAliveCountMax)の可能性が高い。バラつくなら回線品質の問題に寄る。
クライアント側の対策:ServerAlive をどう設定するか?
結論: client の
~/.ssh/configにServerAliveInterval 60を入れるのが第一手。無通信でも 60 秒ごとに暗号化チャネル経由でサーバへ応答要求を送るため、NAT のセッションが維持され、サーバ無応答も検知できる。サーバを触れない環境(共用サーバ等)でも client 側だけで効く。
ServerAliveInterval は「サーバから N 秒データが来なければ、ssh が暗号化チャネル越しに応答要求を送る」秒数。デフォルトは 0(無効)。ServerAliveCountMax(デフォルト 3)は、応答が無いまま送ってよい回数で、これを超えると ssh が切断する。
# ~/.ssh/config
Host *
ServerAliveInterval 60
ServerAliveCountMax 3この設定なら、60 秒ごとに小さなパケットが流れて NAT・firewall のセッションが生き続け、idle 切断を防げる。同時に、サーバが本当に落ちたときは 60 秒 × 3 = 約 180 秒 で見切りをつけて切断する(ゾンビセッションを残さない)。
一時的に試すだけなら -o で指定できる。
$ ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 user@server
Host * に書けば全接続に効く。特定ホストだけにしたいなら Host myserver ブロックに書く。~/.ssh/config のパーミッションは 600 が無難(緩いと無視されることがある)。
ServerAliveInterval を短くしすぎると、瞬断のたびに ServerAliveCountMax を消費して逆に切れやすくなる。回線が不安定な環境では Interval をむやみに詰めず、CountMax を増やす(例: Interval 30 / CountMax 6 で約 180 秒猶予)方向で調整する。
サーバー側の対策:ClientAlive をどう設定するか?
結論: server を管理できるなら
/etc/ssh/sshd_configにClientAliveInterval 60を入れる。sshd が全クライアントに対して暗号化チャネル経由で応答要求を送るので、client 側を個別設定しなくても idle 切断を防げる。逆にidle を積極的に切りたいときも同じパラメータで制御する(CountMax との組み合わせ次第で両用途)。
ClientAliveInterval は client 側 ServerAliveInterval の鏡像で、sshd が「クライアントから N 秒データが来なければ応答要求を送る」秒数。デフォルト 0(無効)。ClientAliveCountMax はデフォルト 3。
# /etc/ssh/sshd_config ClientAliveInterval 60 ClientAliveCountMax 3
設定後は構文チェックしてから反映する。ここで sshd 設定を壊すと自分も締め出されるので、必ず別セッションを開いたまま行う。
# 構文チェック(エラーがあれば反映前に分かる) $ sudo sshd -t # 設定を再読み込み(既存セッションは維持される) $ sudo systemctl reload ssh
# sshd -t がエラーを出さなければ何も表示されない(=正常)
この設定の意味は用途で逆向きになる点に注意する。
- 接続を維持したい:
ClientAliveInterval 60だけで keepalive が流れ、NAT 切断を防げる。CountMaxは大きめでよい - idle を切りたい(セキュリティ要件など):
ClientAliveInterval 300/ClientAliveCountMax 0のように設定すると、無通信のクライアントを約 5 分で切断できる
sshd を restart すると既存接続が落ちる場合がある。設定反映は原則 reload。さらに作業用の SSH セッションをもう 1 本開いたまま sshd -t → reload の順で行い、ロックアウトに備える。ufw 等で自分を締め出した場合の復旧は ufw でSSHが繋がらない時 を参照。
ServerAlive と TCPKeepAlive はどう違うのか?
結論:
ServerAliveInterval/ClientAliveIntervalは SSH の暗号化チャネルを流れるためスプーフ不可で、間隔も秒単位で細かく制御できる。TCPKeepAliveは TCP 層の keepalive で、OS 既定の長い間隔(通常 2 時間起点)に依存し、なりすまし可能。idle 切断対策には SSH 層の ServerAlive 系を使うのが定石。
混同しやすい 3 つを層で整理する。
| 設定 | 層 | 方向 | 備考 |
|---|---|---|---|
ServerAliveInterval |
SSH(暗号化) | client→server | client 側に書く。スプーフ不可 |
ClientAliveInterval |
SSH(暗号化) | server→client | server 側に書く。スプーフ不可 |
TCPKeepAlive |
TCP | 双方向 | 既定 yes。間隔は OS の sysctl 依存 |
TCPKeepAlive yes(OpenSSH の既定)でも一応生存確認は行われるが、TCP keepalive の開始までの遊休時間は OS 設定(Linux では net.ipv4.tcp_keepalive_time、既定 7200 秒=2 時間)に従うため、数分で切る NAT には間に合わない。短い idle タイムアウトを越えてセッションを維持したいなら、秒単位で効く ServerAliveInterval のほうが適切。
# 参考: TCP keepalive の開始遊休時間(秒)を確認 $ sysctl net.ipv4.tcp_keepalive_time
net.ipv4.tcp_keepalive_time = 7200
公式マニュアル(man ssh_config / man sshd_config)は、ServerAlive 系メッセージが「暗号化チャネル経由で送られるためスプーフできない」のに対し、TCPKeepAlive は「スプーフ可能」だと明記している。セキュリティと制御性の両面で SSH 層 keepalive が優先される。
設定しても切れるときは何を確認するか?
結論: keepalive を入れても切れるなら、①間にある NAT / LB の idle タイムアウトが keepalive 間隔より短い、②回線そのものが瞬断している、③サーバ側の別タイムアウト(PAM・ロードバランサのハードリミット)が効いている、のいずれか。keepalive 間隔をその idle 値より確実に短くするのが基本。
keepalive が効かないときの典型を順に潰す。
# 接続が NAT のどの経路を通っているか・経路途中で詰まっていないかの確認 $ ssh -v user@server 2>&1 | grep -iE 'alive|disconnect|timeout'
- NAT / ファイアウォールの idle が短い: 家庭用ルータやクラウド LB は idle タイムアウトが 60〜350 秒程度のことがある。
ServerAliveIntervalをその値より明確に小さく(例: LB が 60 秒なら30)する - 回線の瞬断: 無線 / モバイル回線では物理的に切れる。keepalive では救えないので、後述の tmux / mosh で切断に耐える構成にする
- サーバ側の別リミット: ロードバランサや踏み台のセッション上限、PAM の
pam_exec等が固定時間で切ることがある。journalctl -u sshでサーバ側の切断理由を確認する
# サーバ側ログで切断理由を確認 $ sudo journalctl -u ssh -n 100 --no-pager
クラウド(AWS NLB / GCP 等)のロードバランサ経由では、LB 自体の idle タイムアウト(AWS NLB は既定 350 秒)が支配的になる。keepalive 間隔は必ずこの値より短くする。
keepalive 間隔が idle タイムアウトと同じか長いと意味がない。「LB 60 秒だから ServerAliveInterval 60」では境界でこぼれる。半分以下(この例なら 30 以下)を目安にする。
切断に耐えるセッションには何を使うか?
結論: そもそも切れても作業が飛ばないようにするのが本質的な対策。サーバ上で
tmux/screenを使えば、SSH が切れてもセッションは残り、再接続してattachで戻れる。回線が不安定なら、再接続を自動でこなすmoshが有効。
keepalive は「切らさない」対策、tmux / mosh は「切れても困らない」対策で、併用するのが堅い。
# サーバ側で tmux セッションを開始 $ tmux new -s work # SSH が切れたら再接続して元のセッションに戻る $ ssh user@server $ tmux attach -t work
長時間バッチや危険なコマンドは tmux / screen 内で実行しておけば、回線断でプロセスが道連れに死ぬのを防げる。scp / rsync での大容量転送が途中で切れて困る場合は、再開可能な rsync を使う(ファイル転送の基本 参照)。
mosh(mobile shell)は UDP ベースで、IP が変わっても・回線が一時的に切れても自動で復帰する。モバイルや不安定な回線での運用に向く。ただしサーバ側に mosh のインストールと UDP ポート開放が必要。
まとめとチェックリスト
結論: 「idle で切れる」なら client の
ServerAliveIntervalか server のClientAliveIntervalで keepalive を流すのが第一手。間にある NAT / LB の idle より短い間隔にし、切れても困らないよう tmux / mosh を併用すれば、SSH の突然切断はほぼ解消する。
- [ ] 放置して切れるのか、操作中でも切れるのかを切り分けたか(idle か回線かの二分)
- [ ] client の
~/.ssh/configにServerAliveInterval/ServerAliveCountMaxを設定したか - [ ] server を触れるなら
/etc/ssh/sshd_configにClientAliveIntervalを設定し、sshd -t→reloadしたか - [ ] keepalive 間隔を NAT / LB の idle タイムアウトより確実に短くしたか
- [ ] 設定変更時に作業用 SSH をもう 1 本開いてロックアウトに備えたか
- [ ] 不安定回線では tmux / screen / mosh で切断に耐える構成にしたか
- [ ] それでも切れるなら
journalctl -u sshでサーバ側の切断理由を確認したか