LASSIC Media らしくメディア
Keep-Aliveの設定、待ち時間のずれで502が出る理由
監修・編集責任者:牛尾 昭昌(株式会社LASSIC 執行役員)
この記事の結論
- Keep-Aliveには、接続を使い回すHTTPの持続的接続と、接続の生存を確かめるTCP Keep-Aliveの2つがあります。
- ロードバランサーの後ろでは、バックエンドのKeep-Aliveをアイドルタイムアウトより長くしないと502が出ることがあります。
- 設定は区間ごとに一覧にし、変更の前後でエラーの件数を比べて確かめます。
※ 本記事は2026年10月時点の公式情報(省庁・公的機関、および製品やサービスを提供する事業者が公開している資料)に基づきます。
Keep-Aliveは、一度つないだ接続を閉じずに残し、次の通信にも使い回す仕組みです。ただしこの名前は、HTTPの持続的接続と、TCPが無通信の接続の生存を確かめる機能という、別々の2つを指して使われます。ロードバランサーの後ろに置いたアプリケーションで、ときどき502エラーが出る。原因を追うと、この2つを取り違えたまま値を決めていた、ということが起こります。
本記事では、Webシステムの開発や運用に携わる方に向けて、2つのKeep-Aliveの違い、接続の再利用を確かめるコード例、主な製品の設定項目と落とし穴を、RFCと公式ドキュメントをもとに整理します。
目次
Keep-Aliveとは
HTTPのKeep-Aliveは、1本のTCP接続の上で複数の要求と応答をやり取りする「持続的接続(persistent connection)」のことです。HTTP/1.1の仕様であるRFC 9112は、HTTP/1.1では持続的接続を既定とし、実装はこれに対応すべきだと定めています。*1 接続を張り直すたびに要るTCPの接続確立(3ウェイハンドシェイク)や、HTTPSならTLSのハンドシェイクを省けるのが利点です。
一方のTCP Keep-Aliveは、データのやり取りがしばらく無い接続に確認用の小さなパケットを送り、相手がまだ応答するかを確かめる機能です。HTTPとは関係なく、TCPの層で動きます。名前は同じでも、設定する場所も時間の長さも違います。本記事では、単にKeep-Aliveと書くときはHTTPの側を指します。
HTTP Keep-Aliveの仕組み
HTTP/1.1では、何も指定しなければ接続は応答のあとも残ります。閉じたい側は、Connectionヘッダーにcloseを入れて送ります。closeを受け取ったサーバーは、応答を送ったあとに接続を閉じます。HTTP/1.0では逆に既定が「閉じる」で、「Connection: keep-alive」を付けて持続を頼む古い方式が残っています。
接続を使い回すには、接続を閉じなくても応答の終わりが分かる必要があります。そのため応答はContent-Lengthで長さを示すか、chunked形式で送ります。RFC 9112は、同じ接続で次の要求を送るクライアントは応答の本文を最後まで読み切らなければならない、ともしています。
Connectionヘッダーは、隣り合う相手との1区間だけの(hop-by-hop)情報を示します。RFC 9110は、中継役はConnectionに書かれた項目とConnection自体を取り除いて転送しなければならない、と定めています。*2 つまりKeep-Aliveは、ブラウザからロードバランサー、その先のアプリケーションへと、区間ごとに別々に決まります。ロードバランサーの役割は「ロードバランサとは」で扱っています。
HTTP/2では、1本の接続の上で複数の要求を同時に流す多重化が前提になり、接続の管理は別の仕組みで行います。RFC 9113は、ConnectionやKeep-Aliveといった接続固有のヘッダーを含むメッセージを作ってはならず、含むものは不正な形式として扱う、と定めています。*3
TCP Keep-Aliveとの違い
TCP Keep-Aliveは、1989年のRFC 1122で扱いが定められ、*4 現在のTCPの仕様であるRFC 9293に引き継がれています。RFC 9293は、使う場合は接続ごとにオンとオフを切り替えられ、既定はオフで、確認を始めるまでの無通信の間隔は既定で2時間以上でなければならない、としています。*5
Linuxでは、この間隔などをカーネルのパラメーターで決めます。tcp(7)のマニュアルによると、確認を始めるまでのtcp_keepalive_timeの既定は7200秒(2時間)、確認の間隔を決めるtcp_keepalive_intvlは75秒、回数を決めるtcp_keepalive_probesは9回で、確認はソケットにSO_KEEPALIVEを設定したときだけ送られます。*6
| 観点 | HTTP Keep-Alive | TCP Keep-Alive |
|---|---|---|
| 目的 | 接続を閉じずに次の要求にも使う | 無通信の接続が生きているかを確かめる |
| 働く層 | HTTP(アプリケーション層) | TCP(トランスポート層) |
| 主な設定 | nginxのkeepalive_timeout、ApacheのKeepAliveTimeout、Node.jsのkeepAliveTimeout | ソケットのSO_KEEPALIVE、Linuxのtcp_keepalive_time |
| 時間の長さ | 製品の既定値で数秒から数十秒 | RFCの既定で2時間以上 |
| 無通信のとき | 決めた時間がたつと接続を閉じる | 確認のパケットを送り、応答が無ければ切る |
混同しやすい例がNode.jsです。http.Agentのオプションのうち、keepAliveは要求が無いあいだもソケットを残して次の要求に使う設定、keepAliveMsecsはTCP Keep-Aliveのパケットを送り始めるまでの遅れを決める設定です。公式ドキュメントも、ConnectionヘッダーのKeep-Aliveと混同しないよう注意しています。*7
具体例:接続の再利用を確かめる
まずPythonで確かめます。http.serverで接続元のポート番号を返すサーバーを立て、3回ずつ要求します。番号が同じなら同じ接続です。Python 3.12とRequests 2.34を使い、127.0.0.1の上で動かしました。
import threading, requests
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1" # 持続的接続に対応させる
def do_GET(self):
body = str(self.client_address[1]).encode() # 接続元のポート番号を返す
self.send_response(200)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args): pass
srv = HTTPServer(("127.0.0.1", 8081), Handler)
threading.Thread(target=srv.serve_forever, daemon=True).start()
url = "http://127.0.0.1:8081/"
print("get:", len({requests.get(url).text for _ in range(3)}), "本の接続")
with requests.Session() as s:
print("Session:", len({s.get(url).text for _ in range(3)}), "本の接続")
srv.shutdown()
実行すると「get: 3 本の接続」「Session: 1 本の接続」と表示されました。requests.getを直接呼ぶと毎回新しい接続を張り、Sessionを使うと3回とも1本の接続で済んでいます。Requestsのドキュメントも、Sessionは同じホストへのTCP接続を再利用すると説明しています。*8 protocol_versionをHTTP/1.1にしたのは、http.serverの既定がHTTP/1.0で、HTTP/1.1にすると持続的接続を許す代わりにContent-Lengthが必要になるためです。*9
次はNode.js 24.18で、サーバーが無通信の接続をいつ閉じるかを確かめます。server.keepAliveTimeoutは応答を書き終えてから次のデータを待つ時間で、既定は5000ミリ秒です。例では2秒にしました。
const http = require('node:http');
const server = http.createServer((req, res) => res.end('ok'));
server.keepAliveTimeout = 2000; // 応答後、2秒無通信なら閉じる
server.listen(8082, () => {
const t0 = Date.now();
const req = http.get('http://127.0.0.1:8082/', {
agent: new http.Agent({ keepAlive: true }),
}, (res) => {
console.log('Connection:', res.headers.connection, '/ Keep-Alive:', res.headers['keep-alive']);
res.resume();
});
req.on('socket', (sock) => sock.on('close', () => {
console.log('接続が閉じた:', ((Date.now() - t0) / 1000).toFixed(1), '秒後');
server.close();
}));
});
実行すると「Connection: keep-alive / Keep-Alive: timeout=2」「接続が閉じた: 3.0 秒後」と表示されました。応答のヘッダーでは2秒と知らせ、実際には約3秒後に閉じています。ドキュメントでは、閉じるまでの時間はkeepAliveTimeoutにkeepAliveTimeoutBuffer(既定1秒)を足した値で、ECONNRESETを減らすための余裕とされています。
使いどころ
Keep-Aliveが役立つのは、同じ相手へ短い要求を何度も送る場面です。外部APIを繰り返し呼ぶバッチ処理、マイクロサービスどうしの呼び出し、リバースプロキシーからアプリケーションへの転送などが当たります。Apacheのドキュメントは、画像の多いHTMLで待ち時間がほぼ50%短くなった例がある、と書いています。*10
主な製品の項目と既定値を公式ドキュメントから抜き出すと、次のとおりです。
| 製品 | 項目 | 既定値 | 意味 |
|---|---|---|---|
| nginx | keepalive_timeout | 75s | クライアントとの接続を無通信で残す時間。0で無効*11 |
| nginx | keepalive_requests | 1000 | 1本の接続で受け付ける要求の上限 |
| nginx(upstream) | keepalive | 32(1.29.7以降) | アプリケーション側へ残しておく待機中の接続の数(ワーカープロセスごと) |
| nginx(upstream) | keepalive_timeout | 60s | アプリケーション側の待機中の接続を残す時間 |
| Apache HTTP Server | KeepAliveTimeout | 5(秒) | 次の要求を待つ時間 |
| Apache HTTP Server | MaxKeepAliveRequests | 100 | 1本の接続で受け付ける要求の上限 |
| Node.js | server.keepAliveTimeout | 5000(ミリ秒) | 応答を書き終えてから次のデータを待つ時間 |
| AWS ALB | 接続のアイドルタイムアウト | 60秒(1〜4000秒) | 無通信の接続をロードバランサーが閉じるまでの時間 |
| AWS ALB | HTTP client keepalive duration | 3600秒 | クライアントとの持続的接続を保つ最長の時間 |
nginxのupstreamのkeepaliveは、1.29.7から既定で有効になりました。それより前の版では、keepaliveを書いたうえで、proxy_http_versionを1.1にし、Connectionヘッダーを空にする設定が要ります。*12 使っている版を先に確かめておきます。
つまずきやすい点
最も多いのが、ロードバランサーとバックエンドのタイムアウトのずれです。バックエンドが先に無通信の接続を閉じると、ロードバランサーが気づく前にその接続へ次の要求を送ることがあります。AWSは、アプリケーションのアイドルタイムアウトをロードバランサーより長くするよう勧め、そうしないとクライアントに502 Bad Gatewayが返ることがある、と説明しています。*13 502の意味は「HTTPステータスコードの基礎」で整理しています。
図のように、Node.jsの既定(約6秒)やApacheの既定(5秒)のままALBの既定(60秒)の後ろに置くと、この推奨を満たしません。ALBのトラブルシューティングの資料も、502の原因の一つとして、ターゲットのkeep-aliveの時間がアイドルタイムアウトより短くないかを確かめるよう挙げています。*14
2つ目は、TCP Keep-Aliveで接続が保たれると考えてしまうことです。同じALBの資料は、TCP Keep-Aliveを送ってもアイドルタイムアウトは防げず、時間内に1バイト以上のデータを送る必要がある、と書いています。ALBの属性の説明によると、HTTP/2のPINGフレームでもアイドルタイムアウトはリセットされません。
3つ目は、値を長くしすぎることです。Apacheのドキュメントは、KeepAliveTimeoutを大きくすると無通信のクライアントを待つサーバープロセスが増え、負荷の高いサーバーでは性能の問題を招くことがある、と注意しています。長く残すほど良いわけではなく、前後の機器との大小関係で決めます。タイムアウト全般の決め方は「タイムアウト設計とは」で扱っています。
4つ目は、クライアント側で本文を読み残すことです。Requestsのドキュメントは、本文をすべて読むまで接続はプールに戻らないとしています。stream=Trueで受けたまま閉じ忘れると接続が再利用されず、新しい接続が増えていきます。
外部に委託するときに確認しておきたい点
Keep-Aliveの設定は、アプリケーション、Webサーバー、ロードバランサーと担当が分かれやすい項目です。委託するときは、通信の経路を区間ごとに書き出し、どの製品のどの項目が接続を閉じるのかを一覧にしてもらいます。区間ごとに値の大小関係を説明できるかが確認の目安です。
次に、確かめ方です。設定を変えたあとに、ロードバランサーの502や504の件数と、バックエンドのECONNRESETのようなエラーの記録を、変更の前後で比べる手順があるかを確かめます。ALBであれば、CloudWatchのHTTPCode_ELB_5XX_Countのような指標で追えます。
最後に、設定の置き場所です。設定ファイル、コード、ALBの属性がばらばらに管理されていると、一部だけが変わってずれが再発します。Terraformなどで同じリポジトリに置き、変更をレビューで確かめられる形にしてもらうと保ちやすくなります。
まとめ:Keep-Aliveで確かめておきたい3つの点
Keep-Aliveで確かめておきたい点は3つです。第一に、HTTPの持続的接続とTCP Keep-Aliveは別のもので、設定する場所も時間の長さも違うと押さえること。第二に、Keep-Aliveは区間ごとに決まるため、ロードバランサーのアイドルタイムアウトより、その後ろのバックエンドのKeep-Aliveを長くしておくこと。第三に、値は長ければ良いわけではなく、変更の前後で502などのエラーの件数を比べて確かめることです。
よくある質問
HTTP/2でもKeep-Aliveの設定は要りますか
HTTP/2ではConnectionやKeep-Aliveのヘッダーは使いません。ただし、無通信の接続をいつ閉じるかというタイムアウトは残るため、ロードバランサーやサーバーのアイドルタイムアウトの設定は引き続き確かめます。ALBでは、HTTP/2のPINGフレームを送ってもアイドルタイムアウトはリセットされない点にも注意します。
keepAliveTimeoutはどのくらいの値にすればよいですか
決まった正解はなく、前に置く機器のアイドルタイムアウトより長くするのが基本です。ALBの既定の60秒の後ろであれば、それより数秒長い値が候補になります。長くしすぎると待機中の接続が資源を使い続けるため、実際の負荷で確かめてから決めます。
TCP Keep-Aliveはどんなときに使いますか
データベースへの接続やメッセージの受信待ちのように、長いあいだデータが流れない接続を保ったまま、相手が落ちたことに気づきたい場面で使います。Linuxの既定では確認が始まるまで2時間かかるため、途中のファイアウォールなどに先に切られる場合は、TCP_KEEPIDLEなどのソケットオプションで短くします。
Keep-Aliveとタイムアウト設計のご相談
元請(プライムベンダー)として、Webサーバーとロードバランサーの接続の設計から、エラーの調査、保守・運用までご提案します。
Remoguとリラシクなら、Webシステムの基盤の構築や運用に加わるITエンジニアも探せます。
Remoguは、リモート前提で全国から即戦力のITプロ人材を調達するサービスです。リラシクは、扱う求人がすべてリモートワークのITエンジニア専門転職エージェントです。どちらもLASSICが運営しています。
出典
- *1 参考:IETF「RFC 9112 HTTP/1.1」(https://www.rfc-editor.org/rfc/rfc9112.html)。出典:9.3節(HTTP/1.1は持続的接続が既定・本文を読み切る必要)、9.5節(無通信の接続を閉じる瞬間に新しい要求が送られ得ること)、9.6節(Connection: close の扱い)を参照(2026年10月確認)
- *2 参考:IETF「RFC 9110 HTTP Semantics」(https://www.rfc-editor.org/rfc/rfc9110.html)。出典:7.6.1節(Connectionヘッダーと、中継役が接続ごとの項目を取り除いて転送すること)を参照(2026年10月確認)
- *3 参考:IETF「RFC 9113 HTTP/2」(https://www.rfc-editor.org/rfc/rfc9113.html)。出典:8.2.2節(Connection・Keep-Aliveなど接続固有のヘッダーを含むメッセージの禁止)を参照(2026年10月確認)
- *4 参考:IETF「RFC 1122 Requirements for Internet Hosts — Communication Layers」(https://www.rfc-editor.org/rfc/rfc1122.html)。出典:4.2.3.6節(TCP Keep-Alivesの定め)を参照(2026年10月確認)
- *5 参考:IETF「RFC 9293 Transmission Control Protocol (TCP)」(https://www.rfc-editor.org/rfc/rfc9293.html)。出典:3.8.4節(TCP Keep-Aliveは任意・既定はオフ・間隔は既定で2時間以上・1回の無応答で切断と見なさない)を参照(2026年10月確認)
- *6 参考:Linux man-pages「tcp(7)」(https://man7.org/linux/man-pages/man7/tcp.7.html)。出典:tcp_keepalive_time・tcp_keepalive_intvl・tcp_keepalive_probes の既定値と、SO_KEEPALIVE 設定時だけ送られること、TCP_KEEPIDLE などのソケットオプションを参照(2026年10月確認)
- *7 参考:Node.js「HTTP | Node.js Documentation」(https://nodejs.org/api/http.html)。出典:new Agent の keepAlive・keepAliveMsecs、server.keepAliveTimeout(既定5000ミリ秒)と keepAliveTimeoutBuffer(既定1000ミリ秒)の説明を参照(2026年10月確認)
- *8 参考:Requests「Advanced Usage」(https://requests.readthedocs.io/en/latest/user/advanced/)。出典:Session Objects と Keep-Alive の節(接続プールによるTCP接続の再利用、本文を読み切るまでプールに戻らないこと)を参照(2026年10月確認)
- *9 参考:Python Software Foundation「http.server — HTTP servers」(https://docs.python.org/3/library/http.server.html)。出典:BaseHTTPRequestHandler.protocol_version(既定はHTTP/1.0、HTTP/1.1にすると持続的接続を許しContent-Lengthが必要)を参照(2026年10月確認)
- *10 参考:Apache Software Foundation「Apache HTTP Server Version 2.4 core」(https://httpd.apache.org/docs/2.4/mod/core.html)。出典:KeepAlive・KeepAliveTimeout(既定5)・MaxKeepAliveRequests(既定100)の各ディレクティブの説明を参照(2026年10月確認)
- *11 参考:nginx「Module ngx_http_core_module」(https://nginx.org/en/docs/http/ngx_http_core_module.html)。出典:keepalive_timeout(既定75s)・keepalive_requests(既定1000)・keepalive_time と、listen の so_keepalive パラメーターを参照(2026年10月確認)
- *12 参考:nginx「Module ngx_http_upstream_module」(https://nginx.org/en/docs/http/ngx_http_upstream_module.html)。出典:keepalive(1.29.7以降は既定で有効・ワーカープロセスごとに32)、それ以前に要る proxy_http_version と Connection の設定、keepalive_timeout(既定60s)を参照(2026年10月確認)
- *13 参考:Amazon Web Services「Edit attributes for your Application Load Balancer」(https://docs.aws.amazon.com/elasticloadbalancing/latest/application/edit-load-balancer-attributes.html)。出典:Connection idle timeout(既定60秒・1〜4000秒、アプリケーション側を長くする推奨と502の説明、HTTP/2 PINGではリセットされないこと)と HTTP client keepalive duration(既定3600秒)を参照(2026年10月確認)
- *14 参考:Amazon Web Services「Troubleshoot your Application Load Balancers」(https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-troubleshooting.html)。出典:HTTP 408(TCP Keep-Aliveではアイドルタイムアウトを防げない)と HTTP 502(ターゲットのkeep-aliveがアイドルタイムアウトより短くないか確かめる)の説明を参照(2026年10月確認)