[nginx] Proxy: added the "proxy_allow_duplicate_chunked" directive.

Maxim Dounin mdounin at mdounin.ru
Sun Aug 24 17:15:13 UTC 2025


details:   http://freenginx.org/hg/nginx/rev/cfe8cf095ff0
branches:  
changeset: 9418:cfe8cf095ff0
user:      Maxim Dounin <mdounin at mdounin.ru>
date:      Sun Aug 24 20:14:28 2025 +0300
description:
Proxy: added the "proxy_allow_duplicate_chunked" directive.

This directive allows to accept duplicate "Transfer-Encoding: chunked"
header lines.  These are invalid, and rejected since 8033:2bf7792c262e
(1.23.0), yet it turns out there are quite a few homegrown proxies, notably
Java-based ones, which emit such duplicate headers.

The "proxy_allow_duplicate_chunked" directive makes it possible to enable
compatibility with such proxies by ignoring duplicate "Transfer-Encoding:
chunked" headers instead of rejecting them.

Prodded by Gennady Bekasov,
https://github.com/freenginx/nginx/issues/11

diffstat:

 src/http/modules/ngx_http_fastcgi_module.c   |   4 ++--
 src/http/modules/ngx_http_grpc_module.c      |   1 +
 src/http/modules/ngx_http_memcached_module.c |   1 +
 src/http/modules/ngx_http_proxy_module.c     |  14 ++++++++++++--
 src/http/modules/ngx_http_scgi_module.c      |   4 ++--
 src/http/modules/ngx_http_uwsgi_module.c     |   4 ++--
 src/http/ngx_http_upstream.c                 |   2 +-
 src/http/ngx_http_upstream.h                 |   1 +
 8 files changed, 22 insertions(+), 9 deletions(-)

diffs (139 lines):

diff --git a/src/http/modules/ngx_http_fastcgi_module.c b/src/http/modules/ngx_http_fastcgi_module.c
--- a/src/http/modules/ngx_http_fastcgi_module.c
+++ b/src/http/modules/ngx_http_fastcgi_module.c
@@ -2948,10 +2948,10 @@ ngx_http_fastcgi_create_loc_conf(ngx_con
 
     conf->upstream.intercept_errors = NGX_CONF_UNSET;
 
-    /* "fastcgi_cyclic_temp_file" is disabled */
+    /* the hardcoded values */
     conf->upstream.cyclic_temp_file = 0;
-
     conf->upstream.change_buffering = 1;
+    conf->upstream.duplicate_chunked = 0;
 
     conf->catch_stderr = NGX_CONF_UNSET_PTR;
 
diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c
--- a/src/http/modules/ngx_http_grpc_module.c
+++ b/src/http/modules/ngx_http_grpc_module.c
@@ -4431,6 +4431,7 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t
     conf->upstream.pass_request_headers = 1;
     conf->upstream.pass_request_body = 1;
     conf->upstream.force_ranges = 0;
+    conf->upstream.duplicate_chunked = 0;
     conf->upstream.pass_trailers = 1;
     conf->upstream.preserve_output = 1;
 
diff --git a/src/http/modules/ngx_http_memcached_module.c b/src/http/modules/ngx_http_memcached_module.c
--- a/src/http/modules/ngx_http_memcached_module.c
+++ b/src/http/modules/ngx_http_memcached_module.c
@@ -627,6 +627,7 @@ ngx_http_memcached_create_loc_conf(ngx_c
     conf->upstream.pass_request_headers = 0;
     conf->upstream.pass_request_body = 0;
     conf->upstream.force_ranges = 1;
+    conf->upstream.duplicate_chunked = 0;
 
     conf->index = NGX_CONF_UNSET;
     conf->gzip_flag = NGX_CONF_UNSET_UINT;
diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c
--- a/src/http/modules/ngx_http_proxy_module.c
+++ b/src/http/modules/ngx_http_proxy_module.c
@@ -695,6 +695,13 @@ static ngx_command_t  ngx_http_proxy_com
       offsetof(ngx_http_proxy_loc_conf_t, http09),
       NULL },
 
+    { ngx_string("proxy_allow_duplicate_chunked"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.duplicate_chunked),
+      NULL },
+
 #if (NGX_HTTP_SSL)
 
     { ngx_string("proxy_ssl_session_reuse"),
@@ -3392,6 +3399,7 @@ ngx_http_proxy_create_loc_conf(ngx_conf_
     conf->upstream.request_buffering = NGX_CONF_UNSET;
     conf->upstream.ignore_client_abort = NGX_CONF_UNSET;
     conf->upstream.force_ranges = NGX_CONF_UNSET;
+    conf->upstream.duplicate_chunked = NGX_CONF_UNSET;
 
     conf->upstream.local = NGX_CONF_UNSET_PTR;
     conf->upstream.socket_keepalive = NGX_CONF_UNSET;
@@ -3444,9 +3452,8 @@ ngx_http_proxy_create_loc_conf(ngx_conf_
     conf->ssl_conf_commands = NGX_CONF_UNSET_PTR;
 #endif
 
-    /* "proxy_cyclic_temp_file" is disabled */
+    /* the hardcoded values */
     conf->upstream.cyclic_temp_file = 0;
-
     conf->upstream.change_buffering = 1;
 
     conf->headers_source = NGX_CONF_UNSET_PTR;
@@ -3523,6 +3530,9 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t
     ngx_conf_merge_value(conf->upstream.force_ranges,
                               prev->upstream.force_ranges, 0);
 
+    ngx_conf_merge_value(conf->upstream.duplicate_chunked,
+                              prev->upstream.duplicate_chunked, 0);
+
     ngx_conf_merge_ptr_value(conf->upstream.local,
                               prev->upstream.local, NULL);
 
diff --git a/src/http/modules/ngx_http_scgi_module.c b/src/http/modules/ngx_http_scgi_module.c
--- a/src/http/modules/ngx_http_scgi_module.c
+++ b/src/http/modules/ngx_http_scgi_module.c
@@ -1334,10 +1334,10 @@ ngx_http_scgi_create_loc_conf(ngx_conf_t
 
     conf->upstream.intercept_errors = NGX_CONF_UNSET;
 
-    /* "scgi_cyclic_temp_file" is disabled */
+    /* the hardcoded values */
     conf->upstream.cyclic_temp_file = 0;
-
     conf->upstream.change_buffering = 1;
+    conf->upstream.duplicate_chunked = 0;
 
     ngx_str_set(&conf->upstream.module, "scgi");
 
diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c
--- a/src/http/modules/ngx_http_uwsgi_module.c
+++ b/src/http/modules/ngx_http_uwsgi_module.c
@@ -1578,10 +1578,10 @@ ngx_http_uwsgi_create_loc_conf(ngx_conf_
     conf->ssl_conf_commands = NGX_CONF_UNSET_PTR;
 #endif
 
-    /* "uwsgi_cyclic_temp_file" is disabled */
+    /* the hardcoded values */
     conf->upstream.cyclic_temp_file = 0;
-
     conf->upstream.change_buffering = 1;
+    conf->upstream.duplicate_chunked = 0;
 
     ngx_str_set(&conf->upstream.module, "uwsgi");
 
diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -5292,7 +5292,7 @@ ngx_http_upstream_process_transfer_encod
 
     u = r->upstream;
 
-    if (u->headers_in.transfer_encoding) {
+    if (u->headers_in.transfer_encoding && !u->conf->duplicate_chunked) {
         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                       "upstream sent duplicate header line: \"%V: %V\", "
                       "previous value: \"%V: %V\"",
diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h
--- a/src/http/ngx_http_upstream.h
+++ b/src/http/ngx_http_upstream.h
@@ -182,6 +182,7 @@ typedef struct {
     ngx_flag_t                       intercept_errors;
     ngx_flag_t                       cyclic_temp_file;
     ngx_flag_t                       force_ranges;
+    ngx_flag_t                       duplicate_chunked;
 
     ngx_path_t                      *temp_path;
 


More information about the nginx-devel mailing list