When working with HTTP responses, having both Transfer-Encoding: chunked
and Content-Length
headers is technically invalid according to RFC 7230 Section 3.3.3:
// RFC 7230 states:
"A sender MUST NOT send a Content-Length header field in any message
that contains a Transfer-Encoding header field."
Nginx (especially newer versions) strictly follows HTTP specifications. When it detects chunked encoding:
- Removes Content-Length automatically
- Processes the chunks properly
- May buffer chunks when proxy_buffering is on
If you absolutely need both headers (for legacy client compatibility), try:
location /your-endpoint {
proxy_pass http://backend;
proxy_http_version 1.0; # Downgrade to HTTP 1.0
proxy_set_header Connection "";
chunked_transfer_encoding off; # Disable chunked encoding
}
To inspect Nginx's header processing:
# In nginx.conf
log_format headers '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'ReqHeaders: "$req_headers" RespHeaders: "$resp_headers"';
server {
set $resp_headers "";
add_header X-Debug-Resp-Headers $resp_headers;
location / {
proxy_pass http://backend;
header_filter_by_lua_block {
ngx.var.resp_headers = table.concat(ngx.resp.get_headers(), ", ")
}
}
}
Removing Content-Length with chunked encoding is actually beneficial:
- Avoids client confusion about payload size
- Allows streaming of dynamic content
- Prevents buffer overrun issues
When working with Nginx as a reverse proxy (version 1.2.3 in this case), I encountered an interesting behavior where the proxy automatically removes the Content-Length header when the upstream server responds with chunked transfer encoding. Here's the exact proxy configuration:
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8880;
proxy_buffering off;
proxy_read_timeout 300s;
gzip off;
The backend script returns both headers:
HTTP/1.0 307 Temporary Redirect
Content-length: 251
Pragma: no-cache
Location: /...
Cache-control: no-cache
Transfer-encoding: chunked
After passing through Nginx, the response becomes:
HTTP/1.1 302 Found
Server: nginx/1.2.3
Content-Type: application/json; charset=utf-8
Content-Length: 58
Connection: keep-alive
Location: /...
This behavior stems from HTTP/1.1 specifications (RFC 2616 section 4.4) which states that when Transfer-Encoding is present (especially chunked), the Content-Length header must be ignored. Nginx implements this strictly for HTTP/1.1 compliance.
In earlier versions (pre-1.1), Nginx was more lenient with header combinations, which explains why this "worked" before but doesn't now.
Option 1: Force HTTP/1.0 protocol
proxy_http_version 1.0;
Option 2: Use headers-more module to manually preserve the header
more_set_headers "Content-Length: 251";
Option 3: Modify your application logic to:
if ($http_transfer_encoding ~* chunked) {
# handle chunked responses differently
...
}
Modern HTTP clients should handle chunked encoding properly without requiring Content-Length. If you're dealing with legacy clients, consider:
- Implementing proper chunked encoding handling on client side
- Adding client-side timeout logic
- Using websockets or other protocols if real-time streaming is needed
Removing proxy_buffering (as in your config) is generally good for streaming scenarios but may impact performance for large payloads. Benchmark both approaches:
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;