Cloudflare Compression

Last reviewed on 2026-10-03

Which responses Cloudflare compresses at the edge, which it leaves alone, and how to check what your visitors actually receive.

How Cloudflare decides whether to compress

Cloudflare sits between the visitor and your origin, so it handles the Accept-Encoding negotiation on the visitor side itself. Whether a given response leaves the edge compressed depends on four things:

  1. The visitor's Accept-Encoding. Cloudflare only sends an encoding the client advertised. Browsers advertise br only over HTTPS, so a plain-HTTP test will never show Brotli.
  2. The response Content-Type. Cloudflare compresses a documented list of text-like media types. Anything outside that list is passed through as-is.
  3. Directives from the origin. A Cache-Control: no-transform response header tells intermediaries not to modify the body, and Cloudflare honours it by not compressing.
  4. Your zone configuration. Compression Rules (and, for Workers, the response options you set in code) can change the default behaviour for matching requests.

Content types Cloudflare compresses

Cloudflare publishes the list of media types it compresses by default in its "Content compression" documentation. The list is long and changes occasionally, so treat the summary below as orientation and check the current documentation before relying on a specific type:

GroupExamples on the default list
Texttext/html, text/css, text/plain, text/xml, text/javascript
Scripts and dataapplication/javascript, application/json, application/ld+json, application/manifest+json, application/xml, application/rss+xml, application/xhtml+xml
Fontsfont/ttf, font/otf, application/vnd.ms-fontobject
Images (vector / icons)image/svg+xml, image/x-icon, image/vnd.microsoft.icon
WebAssemblyapplication/wasm

What is not on the list matters just as much. Already-compressed formats (JPEG, PNG, WebP, AVIF, MP4, WOFF2, ZIP) gain nothing from a second pass, and the generic application/octet-stream type is not compressed by default either, because Cloudflare cannot know whether the bytes behind it are compressible.

The WebAssembly / octet-stream trap

The most common reason a .wasm file arrives uncompressed through Cloudflare is that the origin labels it application/octet-stream instead of application/wasm. Older server MIME tables do not know the .wasm extension and fall back to the generic binary type, and Cloudflare then treats the response as opaque binary and skips compression.

WebAssembly modules typically compress well, so this can cost a large share of the transfer size. Fixing the MIME type at the origin solves the compression problem and has a second benefit: WebAssembly.instantiateStreaming() requires the response to be served as application/wasm.

# Nginx (inside http, server or location)
types {
    application/wasm wasm;
}

# Apache (.htaccess or vhost)
AddType application/wasm .wasm

# IIS (web.config)
<staticContent>
  <remove fileExtension=".wasm" />
  <mimeMap fileExtension=".wasm" mimeType="application/wasm" />
</staticContent>

Note that an Nginx types {} block replaces the inherited MIME map in that context, so add the line to your main mime.types file (or include it alongside the defaults) rather than placing a lone types block in a server block. If you cannot change the origin, the alternative is to precompress the file yourself and serve it with the right Content-Encoding; see the precompression guide.

Brotli, gzip and Zstandard at the edge

For responses it compresses, Cloudflare chooses an encoding the visitor accepts. Brotli is used for clients that advertise br, gzip for clients that only advertise gzip. Cloudflare added Zstandard (zstd) as an edge encoding in 2024, so clients that advertise zstd — current Chrome, Edge and Firefox do — can receive it where it is enabled for the zone.

Older tutorials describe a single "Brotli" on/off switch in the Speed settings. Cloudflare has since moved compression control to Compression Rules, which let you match requests (by hostname, path, file extension, or response content type) and choose to disable compression, use the default behaviour, or set a preferred order of algorithms. Available options depend on your plan, so check the dashboard for what your zone offers.

# Typical Compression Rule ideas
# 1. Prefer zstd, then Brotli, then gzip for HTML and JSON
#    when: http.response.content_type.media_type in {"text/html" "application/json"}
# 2. Disable compression for a streaming endpoint
#    when: starts_with(http.request.uri.path, "/api/stream")

How the origin response interacts with edge compression

  • Origin sends uncompressed. Cloudflare compresses at the edge (if the content type qualifies) and caches the result. This is the simplest setup and lets Cloudflare serve the best encoding to each visitor.
  • Origin sends gzip or Brotli. If the visitor accepts that encoding, Cloudflare can pass the compressed body through. If the visitor does not accept it, Cloudflare decompresses before delivery so the client never receives an encoding it did not ask for.
  • Origin sends Cache-Control: no-transform. Cloudflare leaves the body exactly as the origin sent it, compressed or not.

Because Cloudflare normalises Accept-Encoding internally, you do not get one cache entry per distinct browser header string. The general cache-key mechanics are covered in the Vary: Accept-Encoding guide, and the edge-versus-origin decision for all major CDNs in the CDN compression guide.

Cloudflare Workers and Pages

Responses returned from a Worker go through the same edge compression as proxied responses: if the content type qualifies and the client accepts an encoding, Cloudflare compresses the body for you. Do not gzip the body yourself in Worker code and also set Content-Encoding by hand unless you tell the runtime the body is already encoded — the Workers Response constructor accepts encodeBody: "manual" for exactly this case. Without it, a body you compressed yourself can end up encoded twice, which surfaces as a content decoding error in the browser.

// Worker returning a body that is already gzip-compressed
return new Response(gzippedBytes, {
  headers: {
    "Content-Type": "application/json",
    "Content-Encoding": "gzip"
  },
  encodeBody: "manual"
});

Verifying what Cloudflare sends

Test against the public hostname with each encoding in turn. Use a GET request and discard the body rather than relying on -I (a HEAD request), which some stacks answer differently:

for enc in zstd br gzip identity; do
  printf '%-9s' "$enc"
  curl -s -o /dev/null -D - -H "Accept-Encoding: $enc" https://example.com/app.wasm \
    | grep -iE '^(content-encoding|content-type|cf-cache-status):' | tr '\r\n' '  '
  echo
done

If Content-Encoding is missing for every encoding, check the Content-Type first, then look for no-transform in Cache-Control, then review Compression Rules for the path. More tools are listed on the testing tools page.

Frequently asked questions

Does Cloudflare compress application/wasm?

Yes. application/wasm is on Cloudflare's default list of compressible content types. If your .wasm files arrive uncompressed, the origin is most likely labelling them application/octet-stream; fix the MIME type at the origin.

Does Cloudflare compress application/octet-stream?

Not by default. application/octet-stream is a generic binary type, and Cloudflare passes it through unchanged. Serve a specific, accurate Content-Type for compressible files, or precompress them at the origin.

Does Cloudflare support Zstandard (zstd) compression?

Yes. Cloudflare added Zstandard as an edge encoding in 2024. Visitors whose browsers advertise zstd in Accept-Encoding can receive it where it is enabled; Compression Rules control the preferred order of algorithms.

Is there still a Brotli toggle in the Cloudflare dashboard?

Older guides refer to a Brotli on/off switch under Speed settings. Compression is now controlled through Compression Rules, with Brotli used by default for clients that support it. Check the dashboard for the options available on your plan.

Why does my response through Cloudflare have no Content-Encoding header?

The usual causes are a content type that is not on the compressible list, a Cache-Control: no-transform header from the origin, a Compression Rule that disables compression for the path, or a test client that did not send Accept-Encoding (curl only sends it with --compressed or an explicit -H).