nginx only uses br if content-length response header is missing
haarp opened this issue · comments
Hello,
the Brotli nginx module just landed in Debian! Yay!
I gave it a try and installed libnginx-mod-http-brotli-filter on my Debian bookworm install for on-the-fly compression. It is indeed working, but not everywhere.
It seems that br is only being used when there is no content-length
response header (lowercase due to HTTP/2). If that header is present, nginx will use gzip instead.
nginx-1.22.1-2, libnginx-mod-http-brotli-filter 1.0.0~rc-1+b1. Client is Firefox 107.0.
nginx config:
gzip on;
gzip_comp_level 4;
gzip_min_length 250; # not always respected: https://stackoverflow.com/q/33269448/5424487
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript
application/x-json image/svg+xml;
brotli on;
brotli_comp_level 4;
brotli_min_length 250;
brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript
application/x-json image/svg+xml;
gzip_min_length
and brotli_min_length
doesn't seem to make a difference in this behavior. I've tested it with multiple sites, using both PHP over fcgi and reverse proxying. Here are two examples on a reverse-proxy site install.
Successfully uses br (no content-length):
GET /css/Core.css?v=ay495y HTTP/2
Host: domain.net
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:107.0) Gecko/20100101 Firefox/107.0
Accept: text/css,*/*;q=0.1
Accept-Language: en
Accept-Encoding: gzip, deflate, br
Referer: https://domain.net/
Connection: keep-alive
Cookie: ...
Sec-Fetch-Dest: style
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: same-origin
Pragma: no-cache
Cache-Control: no-cache
TE: trailers
->
HTTP/2 200 OK
date: Mon, 12 Dec 2022 19:58:18 GMT
content-type: text/css
cache-control: private, max-age=43200
content-security-policy: default-src 'self' data: 'unsafe-inline' 'unsafe-eval'; object-src 'none'; worker-src 'none'
referrer-policy: same-origin
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
x-permitted-cross-domain-policies: none
x-robots-tag: none
strict-transport-security: max-age=63072000; includeSubdomains
content-encoding: br
X-Firefox-Spdy: h2
Uses gzip instead (content-length present)
GET /css/Layout.css?v=ay495y HTTP/2
Host: domain.net
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:107.0) Gecko/20100101 Firefox/107.0
Accept: text/css,*/*;q=0.1
Accept-Language: en
Accept-Encoding: gzip, deflate, br
Referer: https://domain.net/
Connection: keep-alive
Cookie: ...
Sec-Fetch-Dest: style
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: same-origin
Pragma: no-cache
Cache-Control: no-cache
TE: trailers
->
HTTP/2 200 OK
date: Mon, 12 Dec 2022 19:58:18 GMT
content-type: text/css
content-length: 2085
cache-control: private, max-age=43200
content-encoding: gzip
content-security-policy: default-src 'self' data: 'unsafe-inline' 'unsafe-eval'; object-src 'none'; worker-src 'none'
referrer-policy: same-origin
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
x-permitted-cross-domain-policies: none
x-robots-tag: none
strict-transport-security: max-age=63072000; includeSubdomains
X-Firefox-Spdy: h2
Thanks!
Solved it. Upstream had already compressed certain assets, i.e. those over 1kB in size. nginx will simply not re-compress them, which makes sense. The small gains of ungzip→br wouldn't justify the latency. But nginx will compress uncompressed assets, which results in the Content-Length header being stripped. That's what I was observing here.