How to check content-encoding

Print the response headers and read one line: curl -s -D - -o /dev/null -H 'accept-encoding: gzip, br' https://example.com/ | grep -i content-encoding. The token names how the body was transformed. An absent header means identity, the untransformed body, which is the default the specification assumes.

Why check this

Content-Encoding is the header that decides whether a client can read the bytes at all. A gateway that adds the header without compressing, or a service that compresses twice through two proxies, produces a body no decoder accepts, and the symptom reaches you as an intermittent parse error in one client library and not another. Read it in regression on every route that changed proxy, gateway or CDN configuration, and read it alongside the byte counts so a header that lies gets caught.

Prerequisites

Steps

  1. Step 1.

    Read the header on a route that compresses, and keep the size lines with it.

    curl -s -H 'accept-encoding: gzip' -D - -o /dev/null 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS' | grep -i -E 'content-encoding|content-length|stored-content'
    
    x-goog-stored-content-encoding: identity
    x-goog-stored-content-length: 258732
    content-encoding: gzip
    content-length: 36546

    This origin exposes both numbers. content-length: 36546 is what travels; 258732 is what the object is at rest. The identity token in the storage header is the spelling for "no transformation".

  2. Step 2.

    Read the same header on a route that does not compress.

    curl -s -H 'accept-encoding: gzip' -D - -o /dev/null https://httpbin.org/html | grep -i -E '^HTTP|content-encoding|content-length'
    
    HTTP/2 200
    content-length: 3741

    No header at all. Servers signal "uncompressed" by omitting Content-Encoding, not by sending identity, so an empty grep is the normal negative answer rather than a broken command.

  3. Step 3.

    Separate Content-Encoding from Transfer-Encoding by forcing HTTP/1.1.

    curl -s --http1.1 -H 'accept-encoding: gzip' -D - -o /dev/null https://www.cloudflare.com/ | grep -i -E '^HTTP|encoding'
    
    HTTP/1.1 200 OK
    Transfer-Encoding: chunked
    Vary: accept-encoding
    Content-Encoding: gzip

    Both appear on one response and they are independent. Transfer-Encoding: chunked describes how the message was framed on this hop and says nothing about compression. HTTP/2 has no Transfer-Encoding at all, which is why the same URL over h2 shows only content-encoding.

  4. Step 4.

    Measure the ratio: the same document raw, then with each encoding.

    for e in identity gzip br; do curl -s -o /dev/null -H "accept-encoding: $e" -w "$e %{size_download}\n" 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS'; done
    
    identity 258732
    gzip 36546
    br 30865

    258732 against 30865 is a ratio of 8.4 to 1. A Chrome DevTools capture of the same page on this machine reported encodedBodySize 30865 and decodedBodySize 258732, the same two numbers, so the browser and curl agree byte for byte.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | content-encoding: gzip or br | The body was compressed in transit | Compare the byte counts before trusting it. A header is a claim. | | Header absent | The body is identity, untransformed | Expected on images and video. A defect on HTML, CSS, JS and JSON. | | content-encoding: identity spelled out | Legal and rare | Treat it as uncompressed. Some storage layers write it, most omit the header. | | Transfer-Encoding: chunked alone | Framing, not compression | Do not report it as compression. Look for Content-Encoding separately. | | Two tokens, gzip, gzip | The body was compressed twice | A bug. Decoders apply them in order and most clients give up. Find the second proxy. |

Common mistakes

Sign: The compression ratio comes out as 1 to 1 even though the header says gzip.Cause: The measurement used --compressed. curl then decodes the body, and %{size_download} reports the wire bytes while the saved file holds the decoded bytes. On the Cloudflare home page that reads 105416 on the wire and 1317143 in the file. Send an explicit accept-encoding header for both halves of the comparison.
Sign: curl -I reports no content-encoding and a GET on the same URL reports gzip.Cause: -I sends HEAD, a different method. RFC 9110 section 9.3.2 lets a server omit header fields whose value is only determined while generating the content, and Content-Encoding can be one of them. The five hosts checked here answered HEAD and GET identically, so the mismatch is not universal, but a compression result from -I is not evidence.
Sign: content-length looks far smaller than the page, so the page is reported as tiny.Cause: On a compressed response Content-Length counts the compressed bytes. Reading it as page weight understates the document by the compression ratio, 8.4 times on the page measured above.

What to check next

FAQ

How to check the compression ratio of a response?

Request the URL twice, once with accept-encoding: identity and once with the encoding you care about, and divide the two %{size_download} values. Step 4 does it in one line. The header alone gives no ratio.

How to check if a resource is compressed?

Read content-encoding on that resource, not on the page that loads it. Compression is configured per MIME type, so the HTML can be compressed while a JSON or SVG response from the same host is not.

Does an absent Content-Encoding mean the response is broken?

No. Absent means identity, the untransformed body, which RFC 9110 treats as the default. It is correct on already-compressed formats such as JPEG, WOFF2 and MP4, and a defect on text.

Is Transfer-Encoding: chunked a form of compression?

No. It describes message framing on one hop, so the sender can start writing before it knows the length. A response can be chunked and uncompressed, compressed and not chunked, or both at once, as step 3 shows.

Verified

Verified by Maks Vernycurl 8.21.0

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

basic5 minpublished updated Maks Verny