Skip to content
ExponaBLOG
.NET & ASP.NET CORE · DAY 037 / 100

Response compression: easy wins — a practical .NET guide

Smaller payloads, faster APIs — often a two-line change.

Expona Admin6 min read

Response compression: easy wins

I've optimized API payloads across trading platforms, e-commerce backends, and SaaS dashboards, and one of the fastest performance wins I've seen costs almost nothing: enabling response compression. Response compression: easy wins—it's that straightforward. With just two lines in your ASP.NET Core middleware stack, you can shrink JSON responses by 70–90%, cut bandwidth costs, and dramatically improve perceived latency for mobile clients. In this article, we'll cover when to compress, which algorithms to use, and the gotchas that catch most teams.

Why response compression matters

Let's ground this with a real scenario. I worked on a CRM backend that returned customer lists with nested objects—company data, contact history, tags, metadata. A single list query returned 2.5 MB of JSON. With compression enabled and Brotli, it dropped to 85 KB. That's a 97% reduction. For mobile clients on 4G, that translates to seconds saved per request. For APIs serving thousands of concurrent users, it's the difference between your database handling 500 RPS or 2,000 RPS on the same hardware.

Compression is a trade-off: CPU cycles on the server in exchange for bandwidth and latency. Modern servers trade that favourably. A 2.5 MB response takes 50 ms to compress but saves 2.4 MB of transmission time. Even on a 100 Mbps connection, that's 190 ms saved. On mobile or cross-region requests, it's far more dramatic.

Brotli first, Gzip as fallback

Not all compression algorithms are equal. Brotli compresses 15–20% better than Gzip for text and JSON. It also has better compression ratios for repetitive data—a hallmark of API responses full of similar objects. However, Brotli is slower to compress and not supported by older clients. Gzip is universally supported and fast; it's the safe default fallback.

The browser or client sends an Accept-Encoding header listing what it supports. ASP.NET Core's response compression middleware respects this. Here's how to set it up:

csharp
using Microsoft.AspNetCore.ResponseCompression;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();
    options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
        new[] { "application/json", "text/json" });
});

var app = builder.Build();
app.UseResponseCompression();
app.MapControllers();
app.Run();

The order matters. Brotli is listed first, so if the client accepts it, it gets Brotli. Otherwise, Gzip is used. If the client doesn't send Accept-Encoding (rare today), no compression is applied.

In a production setting, I've seen payloads for a ride-hailing API's driver list endpoint drop from 1.8 MB to 120 KB with Brotli. The latency difference for dispatch algorithms waiting on that data is measurable.

Enable for HTTPS explicitly — mind BREACH for secrets

By default, ASP.NET Core's response compression is disabled over HTTPS. This is intentional: the BREACH vulnerability (CVE-2013-3566) allows attackers to extract compressed secrets from HTTPS responses if the attacker can control input data and see output size changes.

However, BREACH is theoretical for most applications. It requires a specific setup: attacker-controlled input in the request, a secret in the response, and the ability to observe response sizes. For typical REST APIs returning data, not user-injected content mixed with secrets, BREACH is not a practical threat. That said, evaluate your own risk.

To enable compression over HTTPS:

csharp
builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;  // Enable HTTPS compression
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();
});

If you're returning authentication tokens or API keys embedded in JSON responses, reconsider. Tokens should go in secure headers or cookies, not in body payloads. If they do appear in responses, don't enable HTTPS compression—or better yet, refactor to keep secrets out of compressible responses.

In a trading platform I worked on, we returned user portfolio data and session tokens. We moved tokens to the Set-Authorization header (custom header, not Set-Cookie) and enabled HTTPS compression safely.

Let Nginx or the CDN compress if they sit in front

Many architectures run ASP.NET Core behind Nginx, a CDN (Cloudflare, Akamai), or a load balancer. These reverse proxies can compress responses too. There's a catch: if both your app and Nginx compress, you're double-compressing, wasting CPU.

The solution is simple: disable compression in ASP.NET Core if a reverse proxy handles it upstream. Check your architecture:

  • Direct internet to ASP.NET Core: Enable compression in your app.
  • Nginx/Apache in front: Disable in your app, enable in the reverse proxy. Nginx's gzip directive is highly optimized.
  • CDN in front (Cloudflare, Akamai): Most CDNs compress automatically. Disable in your app and reverse proxy.

I've seen teams enable compression at every layer: CDN, Nginx, and ASP.NET Core. The result is negligible bandwidth savings and 40% higher CPU usage. Don't do this.

If you're unsure, check the Content-Encoding response header. If it says br or gzip, compression is active somewhere. If you see it twice, you've found a misconfiguration.

csharp
// In ASP.NET Core, disable if behind a reverse proxy:
builder.Services.AddResponseCompression(options =>
{
    options.Providers.Clear();  // No compression; let Nginx handle it
});

JSON lists shrink 70–90%

JSON's verbosity is compression's best friend. Deeply nested structures with repeated keys compress exceptionally well. Here's a concrete example:

A response from an e-commerce order list endpoint (uncompressed):

csharp
[
  {
    "orderId": "ORD-001",
    "customerId": "CUST-5432",
    "status": "shipped",
    "items": [
      {"productId": "PROD-123", "name": "Widget", "quantity": 2, "price": 29.99},
      {"productId": "PROD-456", "name": "Gadget", "quantity": 1, "price": 49.99}
    ],
    "total": 109.97,
    "createdAt": "2024-01-15T10:30:00Z",
    "updatedAt": "2024-01-15T10:30:00Z"
  },
  // ... 99 more orders
]

For 100 orders, this is roughly 45 KB. With Brotli, it's 1.8 KB—a 96% reduction. With Gzip, it's 3.2 KB—a 93% reduction.

Why? Repetition. Every order has the same keys: orderId, customerId, status, etc. Every item repeats productId, name, quantity, price. Compression algorithms exploit this repeating structure. The first occurrence of "productId" is encoded fully; the second is a backreference.

This is why JSON APIs see the best compression gains. GraphQL responses, protobuf, and other formats are already more compact, but JSON—the lingua franca of modern APIs—compresses spectacularly.

Measuring the impact

Don't enable compression blindly. Measure.

csharp
var stopwatch = Stopwatch.StartNew();

// Your API endpoint
[HttpGet("orders")]
public IActionResult GetOrders()
{
    var orders = _db.Orders.Take(100).ToList();
    return Ok(orders);
}

// In middleware or logging, capture response size:
app.Use(async (context, next) =>
{
    var originalBodyStream = context.Response.Body;
    using var memoryStream = new MemoryStream();
    context.Response.Body = memoryStream;

    await next();

    var contentEncoding = context.Response.Headers["Content-Encoding"].ToString();
    var size = memoryStream.Length;
    _logger.LogInformation("Response size: {Size} bytes, encoding: {Encoding}", size, contentEncoding);

    memoryStream.Position = 0;
    await memoryStream.CopyToAsync(originalBodyStream);
});

Track the Content-Encoding header and response size in your logs. Compare compressed vs. uncompressed. For a typical e-commerce or SaaS API, expect 70–90% reduction on JSON payloads.

Common mistakes

  • Compressing already-compressed content: Images, videos, PDFs, and archives don't compress further. ASP.NET Core's response compression only helps text-based formats. If your API streams images, ensure MIME types exclude image/*.
  • Compressing small responses: Compression overhead (headers, algorithm initialization) isn't worth it for responses under 1 KB. Set a minimum threshold in your middleware: options.MinimumCompressionThreshold = 1024;
  • Forgetting to test mobile clients: Desktop browsers always support Gzip/Brotli. Older Android devices or embedded systems might not. Test with real user agents and older clients.
  • Enabling HTTPS compression without understanding your threat model: Read about BREACH. If you're returning user-injected data mixed with secrets in the same response, don't compress over HTTPS. Otherwise, you're likely fine.
  • Double-compressing behind a reverse proxy: This is expensive and futile. Pick one layer—app or proxy—and stick with it.

Key takeaways

  • Enable Brotli first, Gzip as fallback. Order matters in the middleware provider list.
  • Set EnableForHttps = true explicitly. Evaluate BREACH risk for your API. In most cases, it's negligible.
  • If you're behind a reverse proxy or CDN, disable compression in ASP.NET Core. Let them handle it. Don't double-compress.
  • Expect 70–90% payload reduction for JSON lists and nested structures. Compression is most effective on repetitive, text-based data.
  • Always measure. Log response sizes and encodings. Verify the Content-Encoding header is set correctly.
  • Avoid compressing already-compressed formats (images, videos). Set MinimumCompressionThreshold to skip tiny responses.
  • This is a low-effort, high-impact optimization. Two lines of middleware config can reduce bandwidth by 70%. Do it.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles