﻿id	summary	reporter	owner	description	type	status	priority	milestone	component	version	resolution	keywords	cc	uname	nginx_version
2252	Rate limiting rules turn chunked transfer into invalid request	simon.attila72@…		"We managed to define such a configuration that triggers a very strange behaviour from Nginx.
I am creating this ticket to make sure if it is a bug or an expected behaviour that is not covered by the documentation.

We have a UWSGI Python webapp fronted by Nginx. Nginx receives the requests from AWS ELB.
When we added the following rate limiting rule to Nginx, clients using chunked transfer encoding started to receive ""HTTP 400: Bad Request (Missing body)"" for their POST requests:

{{{
# large file rate limit (per ip)
# large file: >=20MB
# doc: https://www.nginx.com/blog/rate-limiting-nginx/
map $content_length $length_limit {
   # >=100Mb OR 20-99MB
   ""~^([0-9]{9})|([2-9][0-9]{7})"" 1;
    default 0;
}
map $length_limit $limit_key {
    0 """";
    1 $binary_remote_addr;
}
# max 10 large file per minute
limit_req_zone $limit_key zone=req_zone:1m rate=10r/m;
}}}

Tests confirm that above rate limiting rules work as expected (when not using chunked transfer encoding).

If removing the above rules, POSTs with chunked transfer encoding complete successfully again.

The [http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_request_buffering documentation] says `""When HTTP/1.1 chunked transfer encoding is used to send the original request body, the request body will be buffered regardless of the directive value unless HTTP/1.1 is enabled for proxying.""`

Our guess is that in case of chunked transfer encoding Nginx buffers the whole request, then calculates content-length and passes it to our app.
But when adding the above rate limiting rules, the rules reference the ""$content_length"" variable, and initialize it with an empty value (because it is unavailable at the beginning of a chunked transfer). This makes Nginx believe that ""content-length"" is present, so it skips content-length calculation and passes the request along without the content-length header, which finally triggers the ""HTTP 400: Bad Request (Missing body)"" condition.

Above behaviour might be a bug."	defect	closed	minor		nginx-module	1.19.x	fixed		simon.attila72@…	Linux ip-10-1-1-20.eu-central-1.compute.internal 4.14.238-182.422.amzn2.x86_64 #1 SMP Tue Jul 20 20:35:54 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux	"nginx version: nginx/1.20.0
built by gcc 7.3.1 20180712 (Red Hat 7.3.1-13) (GCC)
built with OpenSSL 1.1.1g FIPS  21 Apr 2020
TLS SNI support enabled
configure arguments: --prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/run/nginx.pid --lock-path=/run/lock/subsys/nginx --user=nginx --group=nginx --with-compat --with-debug --with-file-aio --with-google_perftools_module --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_degradation_module --with-http_flv_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_mp4_module --with-http_perl_module=dynamic --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-http_xslt_module=dynamic --with-mail=dynamic --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module --with-threads --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=generic' --with-ld-opt='-Wl,-z,relro -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -Wl,-E'"
