Gzip vs. Brotli and the real tradeoffs between compression ratio and CPU overhead
Compression is essentially always worth enabling for REST APIs, since JSON compresses exceptionally well, the real question is which algorithm to use where. Gzip is the safe, universal default, Brotli gets meaningfully better ratios but at a CPU cost that only pays off in the right circumstances.
Table of Contents
- 1. Why compression matters for REST APIs at all
- 2. Gzip: the established standard
- 3. Brotli compared: better ratio, higher CPU cost
- 4. When Brotli is actually worth it over Gzip
- 5. Configuring compression at the web server level vs. the application level
- 6. Content-Encoding headers and content negotiation
- 7. Weighing CPU overhead against compression level
- 8. Pitfalls with already-compressed payloads
- 9. Gzip or Brotli: the decision at a glance
- 10. Summary
- 11. FAQ
1. Why compression matters for REST APIs at all
JSON responses are made up largely of repeating structures: identical field names in every array element, similar values, plenty of text formatting from quotes, commas, and curly braces. That redundancy makes JSON a format that compresses exceptionally well, often shaving seventy to eighty percent off the original size without the client noticing anything, since decompression happens transparently inside the HTTP client.
For APIs with large list responses, mobile clients on constrained bandwidth, or high request frequency, this effect quickly adds up to noticeably less traffic and shorter load times. Compression is therefore essentially always enabled on modern REST APIs, the real question is not whether but which algorithm, and at which layer of the infrastructure.
2. Gzip: the established standard
Gzip is built on the DEFLATE algorithm and has been a fixed part of practically every HTTP client and web server since the nineties. Its compression level can be set anywhere from 1 (fast, low ratio) to 9 (slow, high ratio), and in practice a middle value between 4 and 6 is usually chosen as a good compromise between CPU cost and achieved size reduction.
Gzip's big advantage is its near-universal support: every browser, every programming language, and practically every HTTP proxy can handle Gzip without any extra configuration. For teams that want to play it safe and avoid edge cases with older clients, Gzip therefore remains the obvious default, even though newer algorithms achieve better compression ratios.
3. Brotli compared: better ratio, higher CPU cost
Brotli was developed by Google specifically for web content and typically achieves a ten to twenty percent better compression ratio than Gzip on text and JSON content at a comparable setting, partly thanks to a built-in dictionary tuned for typical web content. Its quality level ranges from 0 to 11, and the highest levels take noticeably more CPU time than the Gzip equivalent.
That CPU overhead at high quality levels is exactly the tradeoff that matters: for static assets that get compressed once ahead of time, the CPU time needed plays no role at all, since compression happens at build or deploy time rather than on every single request. For dynamically generated API responses that need to be freshly compressed on every request, a high Brotli quality level can noticeably hurt response time instead, especially under high concurrent load.
# nginx.conf: compression for the REST API
http {
gzip on;
gzip_types application/json application/javascript text/css;
gzip_comp_level 5;
gzip_min_length 512;
gzip_vary on;
brotli on;
brotli_types application/json application/javascript text/css;
brotli_comp_level 5;
brotli_min_length 512;
location /api/ {
# Dynamic responses: moderate quality level due to CPU cost
add_header Vary "Accept-Encoding";
}
location /static/ {
# Static assets: pre-compressed once at quality level 11
brotli_static on;
gzip_static on;
}
}
4. When Brotli is actually worth it over Gzip
The decisive distinction is whether content is static or dynamic. Static assets such as JavaScript bundles, CSS files, or a fixed OpenAPI specification file rarely change and can therefore be pre-compressed once at the highest Brotli quality level (11) and served as a ready-made .br file, the maximum compression ratio costs no ongoing CPU time here.
Dynamic API responses, by contrast, are generated fresh on every request and need to be compressed in real time, here a lower Brotli quality level (roughly 4 to 6), or depending on load even Gzip, remains the more practical choice. A blanket Brotli is always better ignores this fundamental difference between one-time pre-compression and repeated live compression under production load.
5. Configuring compression at the web server level vs. the application level
Compression should be configured at the web server level in the vast majority of cases, for example directly in Nginx through the gzip and brotli modules, rather than inside the PHP application itself. The web server is optimized for this task, can handle compression efficiently alongside other requests, and fully offloads the PHP process from work that has nothing to do with business logic anyway.
Compressing directly inside the application, for instance through PHP's own ob_gzhandler function, usually only makes sense when there is no access to the web server configuration, for example on certain shared hosting setups. On self-managed infrastructure with Nginx or a comparable reverse proxy, there is practically no reason to force compression at the application level when the web server handles the same job more efficiently.
6. Content-Encoding headers and content negotiation
A client signals which compression methods it can handle through the Accept-Encoding header, typically Accept-Encoding: gzip, br, deflate. The server then picks the best supported method and marks the actually used compression in the response's Content-Encoding header, for example Content-Encoding: br. If that header is missing, the client assumes the response arrives uncompressed.
If the response is also served through an HTTP cache or a CDN, the Vary header must include Accept-Encoding, otherwise clients with different compression support risk incorrectly getting a cached version compressed for a different encoding. A missing Vary header is a common cause of broken responses behind CDNs, where a client that only supports Gzip suddenly receives a Brotli-compressed response.
7. Weighing CPU overhead against compression level
The practical tradeoff between compression ratio and CPU cost is best managed through concrete compression levels rather than a blanket more is better rule. For dynamic API responses under production load, Gzip level 5 or 6 usually delivers the best ratio of size reduction to CPU time per request, while level 9 barely adds further savings but costs noticeably more compute.
Brotli follows a similar pattern: level 4 to 6 is a sensible range for dynamic, frequently changing content, while level 10 or 11 should stay reserved for content compressed once and served statically. Teams that skip this distinction and apply the highest quality level to everything risk noticeably higher response times under load, without the extra compression gain ever actually reaching the user.
8. Pitfalls with already-compressed payloads
A frequently overlooked pitfall involves JSON responses that already contain compressed binary data, for example images or PDFs embedded directly into a JSON field as a base64 string. Image formats like JPEG or PNG are already heavily compressed themselves, so a further round of Gzip or Brotli compression barely reduces size any further, yet it still costs the full CPU time for the compression attempt.
The better solution is not embedding binary data like images as base64 in a JSON response at all, and instead returning a separate URL to the binary object, served on its own without the roughly thirty-three percent size overhead that base64 encoding adds. If base64 embedding is unavoidable for compatibility reasons, it is worth deliberately disabling compression for those specific responses, or dropping it to a low level, to avoid unnecessary CPU load.
9. Gzip or Brotli: the decision at a glance
The choice between Gzip and Brotli mainly comes down to whether content is static or dynamic, and how much CPU budget is actually available for compression under load. The table below summarizes the key decision criteria.
| Criterion | Gzip | Brotli | Recommendation |
|---|---|---|---|
| Compression ratio on text/JSON | Good | Roughly 10-20% smaller | Brotli with sufficient CPU budget |
| CPU overhead at high quality levels | Moderate | Noticeably higher | Gzip for dynamic responses under load |
| Client support | Near-universal | Very widespread, occasional gaps | Configure Gzip as a fallback |
| Fit for static assets | Good | Very good with pre-compression | Brotli quality 11 offline for assets |
| Fit for dynamic API responses | Very good, low latency | Good at low quality levels | Gzip level 5-6 as the API default |
Mironsoft
OpenAPI design, Symfony APIs, and API security
APIs that external teams can integrate without back-and-forth questions?
We review existing REST APIs for inconsistent error formats, missing OpenAPI documentation, and security gaps, then build an API that is clearly documented, versioned, and hardened against abuse.
API Review
Checking the OpenAPI spec, error formats, and status codes for consistency.
Symfony Implementation
Using DTOs, Serializer, and Validator for clean, type-safe request/response models.
Security Audit
Hardening rate limiting, auth schemes, and input validation against real attack surfaces.
10. Summary
Gzip vs. Brotli: The Essentials at a Glance
Core problem
JSON compresses exceptionally well, but high compression levels cost noticeable CPU time, especially for dynamically generated responses.
Gzip vs. Brotli
Brotli typically achieves 10 to 20 percent better compression ratios than Gzip on text, at the cost of noticeably higher CPU overhead at high quality levels.
Configuration layer
Compression belongs at the web server level (Nginx), not in the application, so the PHP process stays fully offloaded.
Practical advice
Pre-compress static assets at Brotli level 11, compress dynamic API responses live with Gzip level 5-6 or a low Brotli level.