The Accept-Encoding HTTP Header

Last reviewed on 2026-04-28

How browsers and clients tell servers what compressed responses they understand — and what servers actually do with the information.

What the header is for

Every time a browser, mobile app, or backend service sends an HTTP request that can carry a body in response, it can include an Accept-Encoding request header. The header lists the content-coding tokens the client is willing to accept on the response. The server picks one (or none), applies the corresponding compression to the response body, and sets a matching Content-Encoding header so the recipient knows how to decode it.

The mechanism is part of HTTP's broader content negotiation framework. Accept-Encoding is the only piece of that framework most sites actually use today — Accept-Charset has been deprecated, Accept-Language rarely changes server behaviour outside translation pipelines, and Accept is mostly used for API versioning. Compression is the one place where the negotiation pays off on every request.

Syntax in one paragraph

The header is a comma-separated list of "codings", each of which can carry an optional quality value (q-value) between 0 and 1. The grammar, defined in RFC 9110 § 12.5.3, looks like this in practice:

Accept-Encoding: gzip
Accept-Encoding: gzip, deflate, br
Accept-Encoding: br;q=1.0, gzip;q=0.8, *;q=0.1
Accept-Encoding: gzip;q=1.0, identity;q=0.5, *;q=0
Accept-Encoding: identity;q=0
Accept-Encoding: *

Each token names a coding: gzip, deflate, br (Brotli), zstd (Zstandard), compress (historical, never use), identity (no transformation), or the wildcard *. A token without a q-value is treated as q=1.0. A token with q=0 means "do not send this encoding."

How q-values are used (and ignored)

The q-value tells the server the client's preference order. In principle, the server should pick the acceptable coding with the highest q-value. In practice, almost every production server picks based on its own algorithm preference and only consults q-values when ties or exclusions need to be resolved. The reason is straightforward: a server that already has a Brotli-compressed cache entry for a URL is going to send it whenever the client lists br at any non-zero q, because the alternative is wasting CPU recompressing a response the client said it could accept.

Where q-values do matter is when a client wants to forbid a coding it would otherwise be eligible for. br;q=0 is a directive: do not send Brotli even if you have it. Some debugging proxies use this to force a server to fall back to gzip. A client that does not want any compression at all sends identity alone, or any other value combined with identity;q=1, *;q=0.

The role of identity

The identity coding represents the absence of any encoding. Per RFC 9110, identity is implicitly acceptable unless the client explicitly forbids it with identity;q=0 or with a *;q=0 that the client has not overridden by listing identity separately at a non-zero q. In other words, "send me uncompressed" is always the safe fallback unless the client says otherwise.

This matters for an unusual but real case: a streaming endpoint where the server cannot afford the latency or memory of compressing the stream and would rather send identity. As long as the client did not write identity;q=0, the server is allowed to do that and the client must accept it.

The wildcard *

The wildcard matches any coding the client has not already listed. Accept-Encoding: * on its own means "send anything you like, including identity." Accept-Encoding: gzip, *;q=0 means "I accept gzip, and nothing else — not even identity." That second form is the way a client can demand a compressed response. It is rare in practice because almost every client is happy to fall back to identity, but it is the only valid way to write "compression required."

What happens when no Accept-Encoding is sent

If a client omits the header entirely, the server may use any encoding it likes — including identity. Most production servers in this case default to identity, because the absence of the header most often indicates an old or non-standard client that probably cannot decode anything else. The exception is HTTP/2 and HTTP/3 traffic from modern browsers, which almost always include the header; the omission case is therefore mostly seen in API clients and bespoke tooling.

What servers actually do with the value

The negotiation logic on a typical server is roughly:

  1. Parse Accept-Encoding into the set of codings the client accepts (q > 0) and the set it forbids (q = 0).
  2. Filter the server's own ordered preference list (typically br > zstd > gzip > deflate > identity) by what the client accepts.
  3. If a precompressed copy exists for the chosen coding (e.g. via gzip_static or brotli_static in Nginx), serve it.
  4. Otherwise, compress on the fly at the configured level.
  5. Set Content-Encoding to the chosen coding and Vary: Accept-Encoding on the response.

Step 5 is the one easiest to forget. Without Vary: Accept-Encoding, an upstream cache can serve a Brotli body to a gzip-only client and break decoding. The dedicated guide on Vary: Accept-Encoding covers the implications of that header in detail.

A minimal worked example

You can observe the negotiation directly with curl:

$ curl -H "Accept-Encoding: br, gzip" -I https://example.com/style.css
HTTP/2 200
content-type: text/css
content-encoding: br
vary: Accept-Encoding
content-length: 4827

$ curl -H "Accept-Encoding: gzip" -I https://example.com/style.css
HTTP/2 200
content-type: text/css
content-encoding: gzip
vary: Accept-Encoding
content-length: 5910

$ curl -H "Accept-Encoding: identity" -I https://example.com/style.css
HTTP/2 200
content-type: text/css
vary: Accept-Encoding
content-length: 28104

The same URL produces three different bodies depending on what the client says it accepts. Each is correct; each is keyed in the server's cache by the Accept-Encoding value.

Specification history

The header dates back to RFC 2616 (1999), which defined HTTP/1.1. It was carried forward unchanged in semantics by RFC 7231 (2014) and again by RFC 9110 (2022), which is now the definitive reference for HTTP semantics across versions 1.1, 2, and 3. Implementations have remained backward-compatible throughout, which is why a 25-year-old server still negotiates correctly with a current browser as long as both speak HTTP/1.1 or higher.

New codings have been added by registration with IANA. The current registry includes gzip (RFC 1952), deflate (RFC 1951 / 1950), br (RFC 7932), and zstd (RFC 8478). The compress coding from early HTTP is registered but no longer used in practice.

Common misuses

  • Rewriting from JavaScript. Browsers forbid setting Accept-Encoding from fetch() or XMLHttpRequest. The browser's HTTP stack always overrides whatever you tried to send. Server-side detection is the only reliable signal. See the browser-compatibility guide for why.
  • Confusing it with Content-Encoding. Accept-Encoding is a request header announcing what the client supports. Content-Encoding is a response header declaring what the server actually applied. They are mirror-image headers, not the same header in two directions.
  • Confusing it with Transfer-Encoding. Transfer-Encoding (typically chunked) controls how the message body is framed on the wire; it is hop-by-hop and is removed by intermediaries. Content-Encoding is end-to-end and is preserved by every intermediary. The Accept-Encoding header negotiates the latter, never the former.
  • Forgetting Vary. Sending different bodies for the same URL based on a request header is exactly the situation Vary exists for. Without it, caches will serve the wrong body to half your clients. The Vary guide covers the cache-key consequences in detail.

Where to go from here

For the algorithms the header negotiates, see the dedicated pages: gzip, Brotli, deflate, and Zstandard. For configuring servers and CDNs to honour the header correctly, the Nginx, Apache, IIS, and CDN guides walk through real configurations. If a response that should be compressed is not arriving compressed, the troubleshooting guide isolates each step in the chain.