The Content-Encoding Header

Last reviewed on 2026-10-03

The response-side half of HTTP compression: what the header promises, what it does not, and the mistakes that break decoding.

What Content-Encoding says

Content-Encoding names the compression (or other coding) that has been applied to the message body. A response with Content-Encoding: gzip tells the recipient: "these bytes are gzip data; decompress them to get the representation described by Content-Type." The Content-Type still describes the decoded data — a gzip-compressed HTML page is Content-Type: text/html plus Content-Encoding: gzip, not application/gzip.

On responses, the server should only use a coding the client listed in Accept-Encoding. Browsers decode the body transparently, which is why DevTools shows two sizes for every compressed resource.

Accept-Encoding vs Content-Encoding

Accept-EncodingContent-Encoding
Sent inRequestsResponses (and request bodies)
Meaning"I can decode these""This body is encoded with this"
ValuesA list, optionally with q-valuesThe coding(s) actually applied, in order
Typical examplegzip, deflate, br, zstdbr

If you are decoding a specific request string such as gzip, deflate, br, see Accept-Encoding values explained.

Registered values

Content codings are registered with IANA in the HTTP Content Coding Registry. The ones you meet on the web are:

  • gzip (and the legacy alias x-gzip) — see gzip.
  • br — Brotli, see Brotli.
  • zstd — Zstandard, see Zstandard.
  • deflate — zlib-wrapped DEFLATE. Historically some servers sent raw DEFLATE under this name, which is why clients learned to accept both and why deflate is best avoided.
  • compress / x-compress — obsolete LZW coding.
  • identity — no coding. It is meaningful in Accept-Encoding; a response should simply omit Content-Encoding instead of sending identity.

The registry also contains specialised codings such as aes128gcm (used by Web Push) and the dictionary-based codings used by Compression Dictionary Transport. Ordinary web servers do not produce these unless you configure them deliberately.

Multiple codings

The header can list more than one coding, in the order they were applied: Content-Encoding: gzip, br means "gzip first, then Brotli on top", so the recipient must undo Brotli, then gzip. Legitimate stacked codings are very rare in practice. When you see one, it is almost always a bug — typically an application that compresses its own output behind a server or proxy that compresses again. Browsers vary in how well they cope, and many API clients do not support stacked codings at all; see decoding errors.

Content-Encoding vs Transfer-Encoding

Transfer-Encoding is a hop-by-hop property of one HTTP/1.1 connection; Content-Encoding is an end-to-end property of the representation. In practice:

  • Transfer-Encoding: chunked is how HTTP/1.1 streams a body whose length is not known in advance. It is unrelated to compression, and a gzip response produced on the fly is often also chunked.
  • Transfer-Encoding: gzip exists in the specification but is essentially unsupported by browsers and servers. Use Content-Encoding for compression.
  • HTTP/2 and HTTP/3 do not use Transfer-Encoding at all; framing replaces chunking. Content-Encoding works identically on every HTTP version.

Content-Length, transfer size and resource size

When a body is compressed, Content-Length is the length of the encoded bytes on the wire, not the decompressed size. A proxy or middleware that decompresses or recompresses a body must recompute or remove Content-Length; forgetting to do so is a classic cause of truncated or hanging responses.

Browser DevTools reflect the same split. In the Network panel, the transferred size is the compressed size plus headers, and the resource (or "size") column is the decoded size. If both numbers are roughly equal for a text resource, the response was not compressed.

ETags, caching and ranges

  • ETags. A gzip body and a Brotli body of the same file are different byte sequences. Servers handle this differently: Nginx, for example, downgrades a strong ETag to a weak one (W/"...") when it compresses on the fly. Do not be surprised by the W/ prefix.
  • Caching. Because the body depends on the request's Accept-Encoding, compressed responses need Vary: Accept-Encoding so shared caches store the variants separately.
  • Range requests. Byte ranges apply to the encoded representation. Servers commonly disable on-the-fly compression for range requests, or ignore the Range header for compressed responses, so resumable downloads of large files are usually served uncompressed.

"Content-Encoding: gzip" on a file download

Serving a .gz archive is a different thing from compressing a response. If a user should download backup.tar.gz as a file, send Content-Type: application/gzip and no Content-Encoding. If you add Content-Encoding: gzip, the browser decompresses the body on the fly and saves an uncompressed file under a .gz name. The same confusion affects .svgz files: when served over HTTP they need Content-Type: image/svg+xml plus Content-Encoding: gzip, but opened from the local file system there is no HTTP header to tell the browser to decompress them.

Checking the header

# Ask for gzip and show only the relevant headers
curl -s -o /dev/null -D - -H "Accept-Encoding: gzip" https://example.com/ \
  | grep -iE '^(content-encoding|content-length|content-type|vary|etag):'

# Download the raw (still compressed) body and inspect the first bytes
curl -s -H "Accept-Encoding: gzip" https://example.com/ | head -c 4 | xxd
# 1f8b.... = gzip magic number

More approaches are covered on the testing tools page.

Frequently asked questions

What is the difference between Accept-Encoding and Content-Encoding?

Accept-Encoding is a request header listing the codings the client can decode. Content-Encoding is the header on a message that states which coding was actually applied to its body.

Is Content-Length the compressed or uncompressed size?

The compressed size. Content-Length always counts the bytes of the message body as sent, after any content coding has been applied.

What is the difference between Content-Encoding and Transfer-Encoding?

Content-Encoding is an end-to-end property of the representation (such as gzip compression). Transfer-Encoding is a hop-by-hop HTTP/1.1 mechanism, in practice almost always chunked, and is not used in HTTP/2 or HTTP/3.

Is Content-Encoding: deflate dangerous?

Not dangerous, but unreliable. The name has historically been used for both zlib-wrapped and raw DEFLATE data, and some clients mishandle one or the other. Prefer gzip, Brotli or Zstandard.

Should a response send Content-Encoding: identity?

No. identity is meaningful in Accept-Encoding; an uncompressed response should simply omit the Content-Encoding header.