empty-body HTTP/2 upstream responses are never marked keepalive-safe
Bug Overview
Component: ngx_http_proxy_v2_module.c · Affects: 1.29.4 onward, confirmed still present in 1.31.5
Root cause: ngx_http_proxy_v2_filter_init() short-circuits when headers already carry END_STREAM (i.e. an empty body, e.g. Content-Length: 0):
if (ctx->end_stream) {
...
u->length = 0;
u->pipe->length = 0;
ctx->done = 1;
}This means the generic pipe/read loop never calls ngx_http_proxy_v2_process_frames() — the only other place in the module that ever sets u->keepalive = 1. Every subsequent request opens a fresh connection.
Fix, verified to fully resolve it alone, on stock nginx (TIME_WAIT stays flat under load, "keepalive saving/reusing connection" behaves identically to HTTP/1.1):
if (ctx->end_stream) {
...
u->length = 0;
u->pipe->length = 0;
ctx->done = 1;
if (ctx->in == NULL
&& ctx->output_closed
&& !ctx->output_blocked
&& !ctx->goaway
&& ctx->state == ngx_http_proxy_v2_st_start)
{
u->keepalive = 1;
}
} Expected Behavior
an HTTP/2 upstream connection with a cleanly-completed empty-body response should be pooled and reused on the next request, exactly like HTTP/1.1 already does — instead it's silently closed every time.
Steps to Reproduce the Bug
- Configure an upstream block with keepalive N; and a backend that returns a response with an empty body (e.g. Content-Length: 0, status 200).
- Proxy to it with proxy_pass + proxy_http_version 2;.
- Send two sequential requests to the proxying location.
- Check debug_error_log: expect "get keepalive peer: using connection" on the 2nd request — instead you get "free keepalive peer" with no "saving connection", and a brand-new connection every time.
NGINX Configuration
worker_processes 1;
error_log logs/error.log debug;
events {
worker_connections 1024;
}
http {
upstream backend {
server 127.0.0.1:9999;
keepalive 4;
}
server {
listen 9999;
http2 on;
location / {
return 200 ""; # empty body, Content-Length: 0
}
}
server {
listen 9998;
location / {
proxy_pass http://backend;
proxy_http_version 2;
}
}
}NGINX version and build configuration options
The output of nginx -V:
nginx version: nginx/1.31.5
built by gcc 14.2.1 20250405 (GCC)
configure arguments: --prefix=/tmp/nginx-vcheck/install --with-http_v2_module --with-debugEnvironment where NGINX is being built and/or deployed
- Target OS: Void Linux
Architecture where NGINX is being built and/or deployed
The output of uname -a:
Linux kong 6.18.49_1 #1 SMP PREEMPT_DYNAMIC Wed Sep 2 14:10:52 UTC 2026 x86_64 GNU/LinuxNGINX Debug Log
No response
Additional Context
No response
Source: nginx/nginx