Envoy doesn’t actually decompress responses; it can’t without knowing the original content type.

Here’s a live example. Imagine a backend service that always returns Content-Type: application/json and sometimes compresses it with Brotli (which is becoming more common than gzip for web assets).

# Envoy Configuration Snippet
static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 0.0.0.0, port_value: 10000 }
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route:
                  cluster: some_service_cluster
          http_filters:
          - name: envoy.filters.http.router
            typed_config: {}
          - name: envoy.filters.http.compressor # This is the filter we'll configure
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
              compressor_library:
                name: envoy.compression.brotli
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.compression.brotli.v3.Brotli
                  params:
                    quality_level: 4 # Brotli quality level (0-11)
                    lgwin: 22 # Brotli window size
              # Crucially, we need to tell it WHICH content types to compress.
              # If this is missing, it won't compress anything.
              content_type: application/json
              # We can also specify minimum content length to avoid compressing small responses.
              min_content_length:
                value: 1024
  clusters:
  - name: some_service_cluster
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    # This is a dummy service that returns JSON.
    # In a real scenario, this would be your actual backend.
    # For testing, you can use httpbin.org/json
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: some_service_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: httpbin.org, port_value: 80 }

Now, let’s send a request to our Envoy listener on port 10000 and see what happens. We’ll use curl to demonstrate.

First, a request without Accept-Encoding that results in a response smaller than min_content_length:

curl -v http://localhost:10000/json

You’ll see a Content-Type: application/json header, but no Content-Encoding header. The response body is the raw JSON.

Now, let’s request a compressed response by including the Accept-Encoding header. We’ll also make sure the response is large enough. httpbin.org/json returns a small JSON, so we’ll use /anything and add a large payload.

curl -v -H "Accept-Encoding: br" http://localhost:10000/anything -d '{"data": "'$(head -c 2000 /dev/urandom | base64)'"}'

Here, curl sends Accept-Encoding: br. Envoy’s compressor filter sees this, checks if the Content-Type is application/json, and if the Content-Length is over 1024 bytes. If all conditions are met, it compresses the response body using Brotli and adds Content-Encoding: br to the response headers. The curl client, seeing Content-Encoding: br, automatically decompresses it.

The key takeaway is that Envoy doesn’t guess. The compressor filter needs to be explicitly told what Content-Types to compress, and the client needs to signal its support via Accept-Encoding.

The compressor filter is a powerful tool for reducing bandwidth and improving latency, but its configuration is sensitive to the Content-Type and Accept-Encoding headers. If you’re seeing responses that should be compressed but aren’t, the first place to check is the content_type field in the Compressor filter configuration.

If you configure the compressor filter with content_type: "text/html" and your backend returns Content-Type: application/json, no compression will occur, even if the client sends Accept-Encoding: br.

The next thing you’ll likely run into is wanting to compress multiple content types. The Compressor filter’s content_type field accepts a comma-separated list.

Want structured learning?

Take the full Envoy course →