Cloudflare sending brotli encoded content when requesting gzip encoding

For the past couple months we were debugging an issue in one of our websites and finally where able to narrow it down to some encoding mismatch. For some requests made from mobile devices, explicitly requesting gzip/deflate compressed content, Cloudflare is responding with brotli compressed content, which leads to a net::ERR_CONTENT_DECODING_FAILED on the device itself.

Following a “mocked” request via curl:

curl 'https://domain.tld/_next/static/chunks/8956-a1e057f6fc38402b.js' -H 'authority: domain.tld' -H 'accept: _/_' -H 'accept-language: en-US,en;q=0.9' -H 'cookie: [...stripped...]' -H 'referer: https://domain.tld/' -H 'sec-fetch-dest: script' -H 'sec-fetch-mode: no-cors' -H 'sec-fetch-site: same-origin' -H 'user-agent: Mozilla/5.0 (Linux; Android 13; Pixel 4 Build/TP1A.221005.002.B2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/118.0.0.0 Mobile Safari/537.36 Cordova' -H 'x-requested-with: tld.domain.mobileapp' --compressed -v -o /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\* Trying 104.26.15.143:443...

- Connected to domain.tld (104.26.15.143) port 443 (#0)
- ALPN: offers h2,http/1.1
- (304) (OUT), TLS handshake, Client hello (1):
  } [322 bytes data]
- CAfile: /etc/ssl/cert.pem
- CApath: none
- (304) (IN), TLS handshake, Server hello (2):
  { [122 bytes data]
- (304) (IN), TLS handshake, Unknown (8):
  { [19 bytes data]
- (304) (IN), TLS handshake, Certificate (11):
  { [4225 bytes data]
- (304) (IN), TLS handshake, CERT verify (15):
  { [264 bytes data]
- (304) (IN), TLS handshake, Finished (20):
  { [36 bytes data]
- (304) (OUT), TLS handshake, Finished (20):
  } [36 bytes data]
- SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
- ALPN: server accepted h2
- Server certificate:
- subject: CN=domain.tld
- start date: Oct 23 19:31:28 2023 GMT
- expire date: Jan 21 19:31:27 2024 GMT
- subjectAltName: host "domain.tld" matched cert's "\*.domain.tld"
- issuer: C=US; O=Google Trust Services LLC; CN=GTS CA 1P5
- SSL certificate verify ok.
- using HTTP/2
- h2 [:method: GET]
- h2 [:scheme: https]
- h2 [:authority: domain.tld]
- h2 [:path: /_next/static/chunks/8956-a1e057f6fc38402b.js]
- h2 [accept-encoding: deflate, gzip]
- h2 [authority: domain.tld]
- h2 [accept: */*]
- h2 [accept-language: en-US,en;q=0.9]
- h2 [cookie: [...stripped...]]
- h2 [referer: https://domain.tld/]
- h2 [sec-fetch-dest: script]
- h2 [sec-fetch-mode: no-cors]
- h2 [sec-fetch-site: same-origin]
- h2 [user-agent: Mozilla/5.0 (Linux; Android 13; Pixel 4 Build/TP1A.221005.002.B2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/118.0.0.0 Mobile Safari/537.36 Cordova]
- h2 [x-requested-with: tld.domain.mobileapp]
- Using Stream ID: 1 (easy handle 0x136815400)
  > GET /\_next/static/chunks/8956-a1e057f6fc38402b.js HTTP/2
  > Host: domain.tld
  > Accept-Encoding: deflate, gzip
  > authority: domain.tld
  > accept: _/_
  > accept-language: en-US,en;q=0.9
  > cookie: [...stripped...]
  > referer: https://domain.tld/
  > sec-fetch-dest: script
  > sec-fetch-mode: no-cors
  > sec-fetch-site: same-origin
  > user-agent: Mozilla/5.0 (Linux; Android 13; Pixel 4 Build/TP1A.221005.002.B2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/118.0.0.0 Mobile Safari/537.36 Cordova
  > x-requested-with: tld.domain.mobileapp
  >
  > < HTTP/2 200
  > < date: Fri, 03 Nov 2023 12:41:48 GMT
  > < content-type: application/javascript; charset=utf-8
  > < access-control-allow-origin: \*
  > < cache-control: public, max-age=315360000
  > < content-disposition: inline; filename="8956-a1e057f6fc38402b.js"
  > < content-encoding: br
  > < etag: W/"9b7153177c56de770369517eb6ec3388"
  > < strict-transport-security: max-age=63072000; includeSubDomains; preload
  > < x-matched-path: /\_next/static/chunks/8956-a1e057f6fc38402b.js
  > < x-vercel-cache: HIT
  > < x-vercel-id: dub1::fcmnh-1698331720154-8cd6e65c32a5
  > < expires: Thu, 31 Dec 2037 23:55:55 GMT
  > < vary: Accept-Encoding, User-Agent
  > < cf-cache-status: HIT
  > < age: 285967
  > < report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=4cA%2FR3YYBG26Zlego9ZKrd7Bms3XRnU5XPL5HXo%2FxeTPAmkIB5RenlyV61kZVSB8Q400ylUJOweUW7drWxRsaIQ6r1kSfkEL%2BROKlebMDf8wk%2Fk8oAcIXRVrvnmg%2BqGboRz8"}],"group":"cf-nel","max_age":604800}
  > < nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
  > < server: cloudflare
  > < cf-ray: 8204b18bdde91e6a-FRA
  > < alt-svc: h3=":443"; ma=86400
  > <
  > { [1360 bytes data]
- Unrecognized content encoding type. libcurl understands deflate, gzip content encodings.
  100 1360 0 1360 0 0 16213 0 --:--:-- --:--:-- --:--:-- 17662
- Connection #0 to host domain.tld left intact
  curl: (61) Unrecognized content encoding type. libcurl understands deflate, gzip content encodings.

Is this expected?

No, this is definitely not exptected.

Could you please provide us with the RAW content you get on such a request.
I would like to manually debug it.

Please also tell us a little bit more about your setup:

  • are you using Cloudflare as a Proxy?
  • does this happen when using Workers?
  • does this happen when serving from Cloudflare Pages?
  • which curl version do you test with?

Maybe even a direct link, so we can run our own tests. Other than this, an HAR File to any of the Cloudflare members here would definitely be useful.

The raw content of such a downloaded file is brotli encoded content. I verified this by simply decoding it with the brotli CLI.

an HAR File to any of the Cloudflare members here would definitely be useful

I’m happy to send a HAR file, once I’m able to open a support ticket again (wasn’t possible the last 2 days), although it’s pretty useless, since you’d only see the net::ERR_CONTENT_DECODING_FAILED error for the affected files, like this:

        "response": {
          "status": 0,
          "statusText": "",
          "httpVersion": "",
          "headers": [],
          "cookies": [],
          "content": {
            "size": 0,
            "mimeType": "x-unknown"
          },
          "redirectURL": "",
          "headersSize": -1,
          "bodySize": -1,
          "_transferSize": 0,
          "_error": "net::ERR_CONTENT_DECODING_FAILED"
        },
  • are you using Cloudflare as a Proxy?

This is indeed one of the specialties we have, we are using the Cloudflare Loadbalancer in front of our origins. Our domain points to a Cloudflare LB (proxied), which points to our active/fallback origins (unproxied).

I was also very confused about the fact, that if I disable brotli completely in the Cloudflare domain config, I still getting brotli encoded responses, when actively requesting it via Accept-Encoding: br. A counter test on our staging environment (different Cloudflare domain config without Cloudflare LB in between) yields a different result - once disabled in the domain config - I don’t receive brotli encoded content, but plaintext when explicitly requesting it.

So could the Cloudflare LB be the culprit?

I was able to open a ticket today, for anyone within Cloudflare (#3013084).

1 Like

I guess this is the best way to solve this - it should be something for the devs, since I think this is not a known bug that the community can fix.

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.