TIME_WAIT が必要な理由とポート枯渇
なぜか接続できなくなる謎が解ける。TIME_WAITが2MSL待つ本当の理由と、高負荷サーバーで起きるポート枯渇の原因・正しい対処までを原理から整理できる。
- 1.TIME_WAITは能動クローズ側が2MSL待つ状態で、目的は最終ACKの再送保証と古い遅延セグメントの排除の2つ。
- 2.枯渇するのは多くの場合TIME_WAITのソケット数ではなく、発側のエフェメラルポート(4タプルの組み合わせ)が尽きるため。
- 3.tcp_tw_reuseやSO_REUSEADDRは効く場面が限られる。本命はコネクション再利用(Keep-Alive)と接続先の分散。
TIME_WAIT は「終わりかけ」ではなく安全装置
TIME_WAIT は、能動クローズ側(先に FIN を送った側)が最後の ACK を送った後に入る状態です。コネクションは論理的には終わっているのに、ソケットの一部の情報(4タプルと状態)は 2MSL の間メモリに残り続けます。MSL(Maximum Segment Lifetime)はIPパケットがネットワーク内で生存できる最大時間で、RFC 9293 の規定上は2分、実装上はLinuxで30秒(つまり2MSL=60秒)が一般的です。
なぜ「すぐ消さない」のか。理由は2つあり、どちらも 同じ4タプルを即座に再利用すると壊れる ことに起因します。状態遷移の全体像は /network/tcp-state-machine/ を、IPとポートの対応は /network/socket-port/ を前提にします。
2MSL 待つ第1の理由:最終 ACK の再送保証
4ウェイクローズの最後で、能動クローズ側は受動側の FIN に対する ACK を送ります。この最終ACKが ネットワークで失われる とどうなるか。
能動クローズ側 受動クローズ側
FIN_WAIT_2 CLOSE_WAIT
|<- FIN(seq=v) -----------------|
TIME_WAIT LAST_ACK
|-- ACK(ack=v+1) --X(喪失) |
| (ACK来ない) |
|<- FIN(seq=v) 再送 ------------| ← LAST_ACKのまま再送
|-- ACK(ack=v+1) -------------->|
CLOSED
受動側は LAST_ACK のままタイムアウトでFINを再送します。このとき能動側がすでに CLOSED に落ちていたら、その4タプルに対応する状態はもう無いので、OSは再送FINに対して RST を返してしまいます。受動側から見れば「正常に閉じたはずが最後にリセットされた」という異常終了になります。
TIME_WAIT で待っていれば、再送FINを受け取って もう一度ACKを返せる。最終ACKは確認応答されない(ACKのACKは無い)ため、「相手が再送してくるかもしれない最大時間」だけ待つ必要があり、それが片道MSLの往復ぶん、すなわち2MSLです。
TIME_WAITに入るのは先にFINを送った側だけです。最終ACKを送る責任を負うのが能動クローズ側だからです。サーバーが先に閉じる設計(例:HTTPのConnection: close)ならサーバー側にTIME_WAITが溜まり、クライアントが先に閉じる設計ならクライアント側に溜まります。「サーバーには出ない」は誤解で、どちらが能動クローズするかで決まります。
2MSL 待つ第2の理由:古い遅延セグメントの排除
同じ4タプル(送信元IP・送信元ポート・宛先IP・宛先ポート)で 即座に新しいコネクション を張ると、前のコネクションの遅延セグメントが新接続に紛れ込む危険があります。ネットワーク内をさまよっていた古いパケットが、たまたま新接続のシーケンス番号の受信窓に収まると、アプリは前の通信のデータを今の通信のデータとして受け取ってしまう。これを 古い重複(old duplicate) と呼びます。
MSLはパケットの最大寿命なので、2MSL(往路ぶん+復路ぶん)待てば、前コネクションに属する全セグメントは確実にネットワークから消滅します。初期シーケンス番号(ISN)をランダム化してもこのリスクは完全には消せず、時間で確実に断つのがTIME_WAITの役割です。
TIME_WAITが守るのは「過去の通信の後始末(最終ACK再送)」と「未来の通信の安全(古い重複の排除)」の両方。前者は相手のため、後者は自分の次の接続のため、と整理すると忘れません。
本当に枯渇するのは何か:ソケット数ではなく4タプル
「TIME_WAITが数万個ある」を見て即ち危険と判断するのは早計です。枯渇の本質は、能動側が新規接続を張るときに使える 4タプルの組み合わせが尽きる ことにあります。
発側が connect() するとき、宛先IPと宛先ポートは固定(例:APIサーバーの 10.0.0.5:443)で、送信元IPも普通は1つです。すると新接続ごとに変えられるのは 送信元ポート(エフェメラルポート) だけになります。Linux既定のエフェメラルポート範囲は 32768〜60999 で、約2.8万個。これが上限です。
| 状況 | 枯渇する資源 | 効く対策 |
|---|---|---|
| 1つの宛先へ大量の短命接続を能動クローズ | 発側エフェメラルポート(実質2.8万) | Keep-Alive・接続プール・宛先分散 |
| TIME_WAITソケットがメモリを圧迫 | カーネルメモリ・ハッシュテーブル | tcp_max_tw_buckets で上限を設定 |
| LISTEN側で受けきれない | acceptキュー・FD数 | somaxconn・ファイルディスクリプタ上限 |
重要なのは、宛先が変われば4タプルも変わる点です。同じ送信元ポートでも宛先IPが 10.0.0.5 と 10.0.0.6 なら別の4タプルなので、両方同時に使えます。したがって接続先が多数に分散していればポートはほぼ枯渇しません。枯渇は「1つの宛先へ集中する」構成で初めて顕在化 します。1接続あたりの計算は、利用可能ポート数 ÷ 2MSL秒数 が「その宛先への秒間新規接続数の上限」の目安になります。約28000 ÷ 60 ≒ 毎秒460接続、というオーダー感です。
サーバーが443で待ち受けている「LISTENポート」は1つで、何万接続来ても枯渇しません。各接続は4タプル(送信元IP・送信元ポート・宛先IP・宛先ポート)の組み合わせで区別されるため、宛先ポートが同じ443でも送信元IP・送信元ポートが異なれば別接続として多重化できるからです。枯渇が問題になるのは、自分が能動的に大量のアウトバウンド接続を張る側(リバースプロキシ、APIゲートウェイ、バッチのHTTPクライアント等)です。手前にプロキシやNATを挟む構成では、そのNAT機器のポートが先に尽きることもあります(/network/nat/ 参照)。
チューニングの是非:効くもの・危ういもの
TIME_WAIT対策として挙がる設定は、効く範囲を取り違えると無意味か有害です。
net.ipv4.tcp_tw_reuse(推奨度:条件付きで高):TCPタイムスタンプ(RFC 7323)が有効なとき、発側(outbound) のTIME_WAITソケットを新規接続に再利用できる。古い重複はタイムスタンプで弾けるため安全。あくまでconnect()側の話で、accept()側には効かない。tcp_tw_recycle(推奨度:使用禁止):Linux 4.12 で 削除済み。NAT配下の複数ホストを誤判定してパケットを落とす実害があり、決して有効化しない。SO_REUSEADDR(推奨度:用途が別):主に「再起動時にLISTENポートをTIME_WAIT待たずにbindし直す」ためのもの。発側のポート枯渇そのものは解決しない。- エフェメラルポート範囲の拡大:
ip_local_port_rangeを広げれば上限は増えるが、根本対策ではなく延命策。 - 2MSL(MSL)の短縮:Linuxでは固定で、安易に縮めると本来TIME_WAITが守っていた安全性を損なう。
# 状態別の接続数を集計(TIME_WAITの偏りを把握)
ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c
# 発側のTIME_WAIT再利用(タイムスタンプ前提・outbound限定)
sysctl -w net.ipv4.tcp_tw_reuse=1
# エフェメラルポート範囲とTIME_WAITソケット上限の確認
sysctl net.ipv4.ip_local_port_range
sysctl net.ipv4.tcp_max_tw_buckets
ポート枯渇の最善手は「そもそも接続を張り直さない」こと。HTTPならKeep-Aliveで1本のコネクションを使い回し、アプリ側で接続プールを持つ。これだけで新規4タプルの消費が桁で減ります。HTTP/2やHTTP/3の多重化は同一接続上で多数のリクエストを流すため、この問題に構造的に強い(/network/http-versions/ 参照)。チューニングは「再利用しても張り替えが避けられない」場合の二の矢です。
まとめ
TIME_WAIT は無駄な待機ではなく、最終ACKの再送保証と古い重複セグメントの排除という2つの安全性を2MSLという時間で買う仕組みです。高負荷で問題になる「ポート枯渇」の正体は、TIME_WAITソケットの数そのものではなく、1つの宛先へ集中する発側のエフェメラルポート(4タプル)の枯渇 です。だからこそ第一の対策は接続再利用と宛先分散で、tcp_tw_reuse などのOSパラメータは適用範囲を理解した上での補助に留めるべきです。観測した TIME_WAIT の数だけを見て焦らず、「誰が・どの宛先へ・どれだけ能動クローズしているか」を切り分けることが、上級者の正しい一手です。
ネットワーク Article
TIME_WAIT が必要な理由とポート枯渇を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
TCP
比較で見る軸
難易度: advanced / カテゴリ: ネットワーク / タグ数: 5
導入後に効く点
枯渇するのは多くの場合TIME_WAITのソケット数ではなく、発側のエフェメラルポート(4タプルの組み合わせ)が尽きるため。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- ネットワーク
- タグ数
- 5
判断チェックリスト
- 自社の用途が「TCP / TIME_WAIT」に近いか確認する。
- 強みである「TIME_WAITは能動クローズ側が2MSL待つ状態で、目的は最終ACKの再送保証と古い遅延セグメントの排除の2つ。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。