Zstd compression being served to unsupporting client

What is the name of the domain?

What is the issue you’re encountering

Clients that don’t support zstd are being served served zstd content.

What steps have you taken to resolve the issue?

Clearing the cache mitigates this problem for a while. But clients that already got this response will have it cached.

What are the steps to reproduce the issue?

I don’t know the exact steps to reproduce. It seems like an occasional bug. But I have seen it for two assets each in a different Cloudflare account. Both times the content was being served as zstd, but that may be partially because this is a non-universally supported compression type, so this is what would result in it being noticed.

Once this happens you can reproduce in curl.

Edit: Note that curl does not set any Accept-Encoding header by default, so should not get a compressed response, but it does.

% curl -i https://feedmail.org/
HTTP/2 200 
date: Sun, 09 Mar 2025 18:25:01 GMT
content-type: text/html;charset=UTF-8
etag: "9380077934a928c5696909d42dbe9e84e376ac67"
cache-control: max-age=60,stale-while-revalidate=600,stale-if-error=86400
access-control-allow-origin: *
cross-origin-opener-policy: same-origin
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
vary: accept-encoding
content-encoding: zstd
strict-transport-security: max-age=31536000; includeSubDomains; preload
cf-cache-status: REVALIDATED
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=FkQYjD7NYmubL4%2BiW4i%2BlCx8W%2B7IKMWx6EhKaODqjlhEpPqsSoRkaoj3E8mYnMfxpQpE8GgUPR6s8QhjxpXVki9fdNgSQ4lbYI7cVvMQjocB948rp5HKvVlt6Vi2AlI%3D"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
server: cloudflare
cf-ray: 91dc9cce0dd5ac6c-YYZ
alt-svc: h3=":443"; ma=86400
server-timing: cfL4;desc="?proto=TCP&rtt=6031&min_rtt=1855&rtt_var=4108&sent=5&recv=9&lost=0&retrans=0&sent_bytes=3402&recv_bytes=752&delivery_rate=1326577&cwnd=246&unsent_bytes=0&cid=b75056acbbb6e598&ts=47&x=0"

Warning: Binary output can mess up your terminal. Use "--output -" to tell curl 
Warning: to output it to your terminal anyway, or consider "--output <FILE>" to 
Warning: save to a file.

However I have tried requesting other new assets with zstd and the cache correctly varied and served the right encoding. It seems just that one URL gets in this bad state where it is being served to clients that don’t support it.

This most notably affects iOS browsers as they don’t support zstd.

Hi there,

Cloudflare will use whichever content encoding the client selects on request:

But you can disable any type of compression you feel like with a compression rule.
Browse to “Rules” > “Create Rule” > “Compression Rule”.

Take care.

Yes, that is what should happen but this is not what is being observed.

curl does not request any compression by default, but in the example I provided it is incorrectly being served a zstd file. This should never happen. As far as I understand that example is on its own proof of this bug.

iOS does not appear to support zstd but it is being served it.

This is the problem that I am reporting.


To be clear this negotiation does work most of the time. I don’t know exactly what the bug is but it seems that some cache entries get stuck in some corrupted state where they stop respecting the Accept-Encoding header.

As I said in the original post, I do not know how to reproduce. I have done as you have done and checked first filing a cache entry with a request with Accept-Encoding: zstd and this isn’t enough to trigger the bug. But I have observed it twice.

What I know:

  1. The first time this was noticed was 2025-03-07 05:42:41 with URL https://obscura.net/_astro/proxy.CWKLfWRC.js. (All other URLs seemed fine.)
  2. The second time this was noticed was 2025-03-09 17:23 at https://feedmail.org/
  3. Both times this was noticed by an iOS user. (IDK if this is important for triggering the bug, or just because that is the most common browser that doesn’t support zstd.)
  4. Just filling the cache with a zstd entry is not enough. The behaviour appears to be correct 99% of the time. But there appears to be some edge case where it isn’t, and a single cached URL stops performing content negotiation.

I should emphasize that this isn’t just me playing around with curl and being confused. This is a bug that broke 2 production websites. (One because some critical JS didn’t load due to unsupported compression and one the homepage was corrupted).

The curl command log is just a minimal example of the bug distilling it to the simplest thing that can prove the bug exists. This was after debugging the real issue on the real sites.

Zstandard is the Cloudflare preferred compression algorithm by default. If it’s not supported, it will automatically fall back to Brotli, Gzip, or uncompressed data (in this order).
Feel free to change this behavior with a rule.

For instance:


…or remove zstd completely:

Yes, but it shouldn’t serve it to clients that don’t support it.

I want zstd enabled, it is a good format. But it is buggy behaviour for Cloudflare to serve it when it isn’t offered in the Accept-Encoding request header.

  • Zstd was enabled as the default compression method at the end of last year for free zones.
  • If the client does not specify Accept-Encoding, then it will be used by default.

This is not a bug, it’s the default behavior. You can either make sure the client requests the correct encoding, or, like I suggested above, you can change the order of the compression types so if there is no Accept-Encoding request header, Cloudflare will try to use them in the order you defined in the rule instead of the default order (Zstd > Brotli > Gzip > uncompressed)

Take care.

I feel like you aren’t reading my messages. I will summarize here and try to be very clear.

Firstly, I understand that this works correctly most of the time.

I am talking about a rare edge case that is hard to reproduce. But you can see irrefutable evidence of it occurring from the logs I posted in the original message.

% curl -i https://feedmail.org/
HTTP/2 200 
date: Sun, 09 Mar 2025 18:25:01 GMT
content-type: text/html;charset=UTF-8
etag: "9380077934a928c5696909d42dbe9e84e376ac67"
cache-control: max-age=60,stale-while-revalidate=600,stale-if-error=86400
access-control-allow-origin: *
cross-origin-opener-policy: same-origin
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
vary: accept-encoding
content-encoding: zstd
strict-transport-security: max-age=31536000; includeSubDomains; preload
cf-cache-status: REVALIDATED
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=FkQYjD7NYmubL4%2BiW4i%2BlCx8W%2B7IKMWx6EhKaODqjlhEpPqsSoRkaoj3E8mYnMfxpQpE8GgUPR6s8QhjxpXVki9fdNgSQ4lbYI7cVvMQjocB948rp5HKvVlt6Vi2AlI%3D"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
server: cloudflare
cf-ray: 91dc9cce0dd5ac6c-YYZ
alt-svc: h3=":443"; ma=86400
server-timing: cfL4;desc="?proto=TCP&rtt=6031&min_rtt=1855&rtt_var=4108&sent=5&recv=9&lost=0&retrans=0&sent_bytes=3402&recv_bytes=752&delivery_rate=1326577&cwnd=246&unsent_bytes=0&cid=b75056acbbb6e598&ts=47&x=0"

Warning: Binary output can mess up your terminal. Use "--output -" to tell curl 
Warning: to output it to your terminal anyway, or consider "--output <FILE>" to 
Warning: save to a file.
  1. curl did not send any Accept-Encoding header.
  2. Cloudflare returned zstd encoded data.

This is a bug that breaks websites.

As you can see this is not happening in this case. The expected behaviour is Cloudflare falling back to uncompressed data. But this is not what happened.

This has also been observed on iOS where Cloudflare should have fallen back to a supported compression mechanism, but didn’t and served unsupported zstd, breaking the websites.

Again: This isn’t the common behaviour. 99% of the time this fallback works correctly. But it seems that there is some edge case where individual cached URLs get stuck serving zstd data even if the client does not support it.


Thanks for suggesting the work around to disable zstd using a Compression Rule. I will do that on all of my sites until Cloudflare sorts out this bug. Because having my website randomly broken for iOS users (and other browsers that don’t support zstd) is not acceptable.

Edit: I originally deleted this message to avoid distracting from the underlying bug, but have undeleted it to show that the workaround provided is not sufficient for all users.


Actually I can’t even disable zstd on one of my sites (obscura.net) as the dashboard returns

exceeded the maximum number of rules in the phase http_response_compression: 1 out of 0

Which is strange because it works on another site currently on the free plan.

So it appears that this is breaking sites with no workaround.

I’m the one that feels like you’re not accepting my solution.

As stated above this is the default order:

…so no, it should not default to uncompressed, the lack of the Accept-Encoding request header will make Cloudflare start with “Zstd”, then “Brotli” and so on.

This is the solution to your issue:

1 Like

Your solution doesn’t fix the problem. Adding per-account rules because a default setting works incorrectly is a workaround at best, and I can’t even apply it on one of my accounts. (I have undeleted the message above that I originally deleted to avoid distractions from the core problem.)

so no, it should not default to uncompressed, the lack of the Accept-Encoding request header will make Cloudflare start with “Zstd”, then “Brotli” and so on.

You are correct about this according to the RFC. But this is not how any website I can test behaves. They all return uncompressed responses when Accept-Encoding is not provided. Example on Cloudflare.

curl -v https://feedmail.org/
* Host feedmail.org:443 was resolved.
* IPv6: 2606:4700:3030::6815:1001, 2606:4700:3030::6815:2001, 2606:4700:3030::6815:3001, 2606:4700:3030::6815:4001, 2606:4700:3030::6815:5001, 2606:4700:3030::6815:6001, 2606:4700:3030::6815:7001
* IPv4: 104.21.16.1, 104.21.32.1, 104.21.48.1, 104.21.64.1, 104.21.80.1, 104.21.96.1, 104.21.112.1
*   Trying [2606:4700:3030::6815:1001]:443...
* Immediate connect fail for 2606:4700:3030::6815:1001: Network is unreachable
*   Trying [2606:4700:3030::6815:2001]:443...
* Immediate connect fail for 2606:4700:3030::6815:2001: Network is unreachable
*   Trying [2606:4700:3030::6815:3001]:443...
* Immediate connect fail for 2606:4700:3030::6815:3001: Network is unreachable
*   Trying [2606:4700:3030::6815:4001]:443...
* Immediate connect fail for 2606:4700:3030::6815:4001: Network is unreachable
*   Trying [2606:4700:3030::6815:5001]:443...
* Immediate connect fail for 2606:4700:3030::6815:5001: Network is unreachable
*   Trying [2606:4700:3030::6815:6001]:443...
* Immediate connect fail for 2606:4700:3030::6815:6001: Network is unreachable
*   Trying [2606:4700:3030::6815:7001]:443...
* Immediate connect fail for 2606:4700:3030::6815:7001: Network is unreachable
*   Trying 104.21.16.1:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /nix/store/2vjhfxc2vpval8v76v0hn1d2rpincpgd-nss-cacert-3.108/etc/ssl/certs/ca-bundle.crt
*  CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / x25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=feedmail.org
*  start date: Feb  3 09:39:23 2025 GMT
*  expire date: May  4 10:37:40 2025 GMT
*  subjectAltName: host "feedmail.org" matched cert's "feedmail.org"
*  issuer: C=US; O=Google Trust Services; CN=WE1
*  SSL certificate verify ok.
*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
*   Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384
*   Certificate level 2: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using ecdsa-with-SHA384
* Connected to feedmail.org (104.21.16.1) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://feedmail.org/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: feedmail.org]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.12.0]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: feedmail.org
> User-Agent: curl/8.12.0
> Accept: */*
> 
* Request completely sent off
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 200 
< date: Fri, 14 Mar 2025 13:17:48 GMT
< content-type: text/html;charset=UTF-8
< etag: W/"b5e0aaabfa55ce0052f4a76252be8548f11911f2"
< cache-control: max-age=60,stale-while-revalidate=600,stale-if-error=86400
< access-control-allow-origin: *
< cross-origin-opener-policy: same-origin
< x-content-type-options: nosniff
< x-frame-options: SAMEORIGIN
< vary: accept-encoding
< strict-transport-security: max-age=31536000; includeSubDomains; preload
< cf-cache-status: REVALIDATED
< report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=lwxePIlpj0QoXDil1hT4W%2Fzp9RVrEjh3shs%2B1oDs6ISuUrZckmX9myu2HAKLp2kovyTJSFapVBTu1JllBbY19DiLD2EjcNXa35kM7hwSlNeXJiLnv%2F3mL0NVsuGPLhE%3D"}],"group":"cf-nel","max_age":604800}
< nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
< server: cloudflare
< cf-ray: 92040da9da673a05-YYZ
< alt-svc: h3=":443"; ma=86400
< server-timing: cfL4;desc="?proto=TCP&rtt=2014&min_rtt=1843&rtt_var=508&sent=5&recv=9&lost=0&retrans=0&sent_bytes=3403&recv_bytes=752&delivery_rate=1352747&cwnd=246&unsent_bytes=0&cid=7ddd4073d8862d76&ts=42&x=0"
< 
[Body Omitted]

In this case there was no Accept-Encoding header and Cloudflare responded with uncompressed data (as expected). In cases where the “buggy cache entry” occurs like the original request shown, zstd is (unexpectedly) returned instead.

That is a flaw in my example. But this bug was noticed on iOS and it sets Accept-Encoding: gzip, deflate, br so it should not be getting a zstd response. So it is clear that this isn’t just occasional unusual handling of no Accept-Encoding header, it is much more general than that and compression negotiation seems to be skipped. If I see this bug again I will produce a log with an Accept-Encoding header for completeness. But I think given the shown behaviour and expected behaviour it is still clear that this bug exists.