Back to HTTP Headers

Content-Encoding content both

Describes the compression or transformation applied to the message body, so the recipient knows how to decode it.

What it does

Content-Encoding tells the recipient that the message body has been compressed or otherwise transformed, and specifies which encoding was applied so the recipient can reverse it. After decoding, you get the actual content described by Content-Type.

It most commonly appears on responses (servers compressing responses to save bandwidth), but it's valid on requests too — clients can send compressed request bodies if the server indicates it supports this.

Syntax

Content-Encoding: <encoding>
Content-Encoding: <encoding1>, <encoding2>

Multiple encodings can be listed, applied in order (left to right). The recipient reverses them right to left to decode.

Examples:

Content-Encoding: gzip
Content-Encoding: br
Content-Encoding: deflate
Content-Encoding: gzip, br

Encoding values

Value Description
gzip GNU zip compression (LZ77 + CRC32). The most widely supported encoding.
br Brotli compression. Better compression ratios than gzip, especially for text. Supported by all modern browsers.
deflate zlib structure with deflate compression. Confusingly named — despite the name, raw deflate is less common than gzip.
zstd Zstandard compression. Newer, fast, excellent ratios. Growing adoption.
identity No transformation applied. Rarely stated explicitly — omitting the header implies identity.

How it interacts with other headers

Content-Encoding and Accept-Encoding are a negotiation pair. The client sends Accept-Encoding listing the compression formats it supports; the server picks one, compresses the response body, and declares the choice in Content-Encoding.

Content-Encoding is about the representation — the compression is considered part of the resource's encoding and affects how Content-Length and caching work. The Vary: Accept-Encoding header should accompany compressed responses so caches know to store separate versions per encoding.

Transfer-Encoding is different — it's a transport transformation applied for the current hop only, stripped by the final receiver (or intermediary). Content-Encoding is end-to-end: it travels with the content all the way, and only the final recipient decodes it. A response can technically have both, though it's rare.

Common mistakes and gotchas

Not sending Vary: Accept-Encoding. If a CDN or proxy caches your compressed response and serves it to a client that didn't send Accept-Encoding: gzip, that client receives garbled data. Vary: Accept-Encoding tells the cache to key responses by the client's accepted encodings.

Double-compressing. Some setups have compression at the application layer and again at the web server/CDN layer. The result is a file that's compressed twice, which is almost always larger than compressing once, and causes decoding errors if the Content-Encoding only declares one layer.

Forgetting Content-Encoding on request bodies. If you're sending a compressed request body (e.g., a large JSON payload), you must include Content-Encoding: gzip on the request. Servers that don't expect compressed request bodies will try to parse the raw gzip bytes as JSON and fail spectacularly.

Compressing already-compressed content. Images (JPEG, PNG, WebP), videos, and ZIP files are already compressed. Running gzip over them again usually makes them slightly larger. Most smart servers/CDNs skip compression for these MIME types.

Real-world examples

Compressed HTML response (server → client):

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Encoding: br
Vary: Accept-Encoding
Content-Length: 3241

<brotli-compressed bytes>

Client advertising encoding support:

GET /api/data HTTP/1.1
Accept-Encoding: gzip, br, zstd

Compressed API request body (client → server):

POST /api/bulk-import HTTP/1.1
Content-Type: application/json
Content-Encoding: gzip
Content-Length: 8192

<gzip-compressed JSON bytes>

FAQ

What's the practical difference between gzip and brotli (br)?

Brotli typically achieves 15–25% better compression than gzip for text content (HTML, CSS, JS, JSON). The tradeoff is that brotli is slightly slower to compress, though faster to decompress. For pre-compressed static assets (where you compress once and serve many times), brotli is almost always the better choice. For dynamically compressed responses where compression happens per-request, gzip might be preferred to save CPU. All modern browsers support brotli over HTTPS.

Does Content-Encoding affect Content-Length?

Yes — Content-Length reflects the size of the body after encoding (the compressed bytes). So a 100KB JSON response compressed to 8KB will have Content-Length: 8192, not Content-Length: 102400. Some clients use Content-Length to show download progress, so this matters for UX too.

Can I use Content-Encoding on images or video?

Technically yes, but it's almost never useful. Images like JPEG and PNG and most video formats are already compressed — applying gzip on top will barely reduce size and adds decompression overhead. Serve images and video without Content-Encoding; use optimized formats (WebP, AVIF, AV1) instead.

Is Content-Encoding the same as Transfer-Encoding?

No. Content-Encoding is an end-to-end encoding that's part of the resource itself — it travels all the way from origin to final recipient and affects caching. Transfer-Encoding is a hop-by-hop transformation for the current connection only, stripped before forwarding. In HTTP/1.1, Transfer-Encoding: chunked is the most common transfer encoding and has nothing to do with compression.

Fun fact

Brotli (br) was developed by Google and open-sourced in 2015. Its name comes from a Swiss German word for a bread roll — the compression algorithm was named after a Swiss bakery near the Google Zurich office. It was initially HTTPS-only in browsers because Google wanted to prevent middleboxes from corrupting brotli-encoded content, a real problem that had plagued early deflate deployments on HTTP.