【実務・中級編】 HTTPのKeep-AliveタイムアウトとTCP接続の切断タイミング – ネットワーク基礎とWebセキュリティ実践ガイド

なぜそのコネクションは切れないのか?HTTP Keep-AliveとTCPの「泥沼」を読み解く

現場でインフラを触っていると、必ず一度は「サーバーのリソースが枯渇しているのに、接続数が一向に減らない」という怪現象に遭遇する。監視ツールを見れば ESTABLISHED 状態のTCP接続がズラリ。サーバー管理者は「タイムアウトを設定したはずだ」と首をかしげる。

その原因の多くは、HTTPの Keep-Alive とTCPのタイマー設定の「噛み合わせ」の悪さにある。今日は、パケットがネットワークを駆け巡る現場の視点から、この見えない境界線をどう制御すべきか、深く掘り下げていこう。

—

1. Keep-Aliveの正体:効率化の代償

HTTP/1.1の Keep-Alive は、TCPの3ウェイ・ハンドシェイクを繰り返すコストを削減するための賢い仕組みだ。一度確立したTCP接続を使い回すことで、レイテンシを劇的に改善する。

しかし、この「使い回し」が長すぎれば、サーバーは誰も使わない「幽霊のようなコネクション」を抱え続けることになる。これを制御するのが Keep-Alive タイムアウトと Max-Keep-Alive-Requests だ。

サーバーが管理すべき2つの境界線

1. Keep-Alive Timeout: クライアントが何も送ってこないときに、サーバーが接続を切断するまでの待機時間。
2. Max-Keep-Alive-Requests: 一つの接続で処理できる最大リクエスト数。これを超えると、サーバーは Connection: close を返して強制的に切断する。

ここでの落とし穴は、「OSのTCPタイムアウト設定と、アプリケーション(Webサーバー)のタイムアウト設定は別物である」という点だ。

—

2. 通信シーケンスのリアル:FINパケットの行方

多くのエンジニアが勘違いしているのが、HTTP層でタイムアウトが発生した瞬間にTCP接続が消滅するわけではないということだ。

1. サーバー側: Keep-Alive タイムアウト到達。
2. サーバー側: HTTP層ではなく、TCP層で FIN パケットを送信。
3. クライアント側: ACK を返し、FIN を投げて 4-way handshake を完了させる。

もし、クライアントがこの FIN パケットを無視(あるいはネットワークの途中でドロップ)した場合、サーバー側には FIN_WAIT_1 や FIN_WAIT_2 といった状態のソケットが残り続ける。この「ゴミ」が重なると、ファイルディスクリプタの限界を迎え、新規接続を受け付けなくなる。これが現場でよく見る「突然のサービス停止」の正体だ。

—

3. 実践:Nginxでの適切な設定

Nginxを例に挙げよう。デフォルト値は「運用環境には長すぎる」ことが多い。

http {
    # Keep-Aliveの待機時間は短めに設定する(5秒〜15秒が推奨)
    keepalive_timeout 15s; 
    
    # 1つの接続で許容するリクエスト数。APIサーバーなら多めにしても良い
    keepalive_requests 100;
}

ここで重要なTipsがある。ロードバランサー(ALBやELB)とバックエンドサーバーのタイムアウト設定は、必ず「ロードバランサー側を短く」設定すること。 そうしないと、ロードバランサーが接続を切った後にバックエンドが応答を返そうとして RST パケットを食らうという、無駄な通信が発生する。

—

4. デバッグの現場:クライアント側の振る舞い

開発者が書くクライアントコードでも、この挙動を意識する必要がある。例えば、Pythonの requests ライブラリでセッションを張る場合だ。

import requests

# Sessionを使うとKeep-Aliveが有効になる
session = requests.Session()

# 複数のリクエストを同じ接続で送る
for i in range(10):
    # 接続が維持され、TCPハンドシェイクのオーバーヘッドが減る
    response = session.get('https://api.example.com/data')
    print(f"Status: {response.status_code}")

# 最後に必ず閉じる。これを忘れると接続が掴みっぱなしになる
session.close()

CLIで挙動を確認するなら、curl の -v(verbose)オプションが最強の武器だ。

# -v でHTTPヘッダーと接続状況を追う
curl -v -k https://api.example.com/

出力の中に Connection: keep-alive というヘッダーが見えれば成功している。もし Connection: close が返ってくるなら、サーバー側の keepalive_requests 上限に達している可能性がある。

—

5. まとめ:現場のエンジニアへのアドバイス

ゼロトラスト全盛の今、境界防御の考え方は「どこで接続を切るか」に集約される。

  • Web APIの場合: 接続は短命に。keepalive_timeout は短くし、サーバーのリソースを解放せよ。
  • 高負荷なフロントエンド: 接続は長めに。ただし、Max-Keep-Alive-Requests で強制的にローテーションさせ、メモリリークやセッション固着を防げ。
  • トラブル時: netstat -an | grep ESTABLISHED で接続数を確認し、特定のクライアントIPから異常に多いコネクションが張られていないか確認すること。

ネットワークは生き物だ。仕様書通りの挙動だけでなく、TCPのステート遷移が今どうなっているのかを想像しながら、パケットの呼吸を感じ取ってほしい。それができれば、どんな障害も怖くはないはずだ。

コメント

タイトルとURLをコピーしました