Accept-Encodingは、クライアントが受け取れる圧縮方式(コンテンツコーディング)をサーバーへ伝えるHTTPリクエストヘッダです。サーバーはこの値を見て圧縮方式を選び、実際に使った方式をレスポンスのContent-Encodingで返します。この記事ではRFC 9110の解釈ルール、ブラウザや各HTTPクライアントが実際に送る値、圧縮の確認手順、サーバー設定で見落としやすい既定値を、2026年9月28日時点の一次情報と手元での実行結果をもとに整理します。
まとめ:Accept-Encodingの要点
- Accept-Encodingは「受け取れる圧縮方式」の申告で、サーバーが選んだ結果は
Content-Encodingに出る。 - 主な値は
gzip・br(Brotli)・zstd(Zstandard)・deflate。現行のChrome・Firefoxはgzip, deflate, br, zstdを送り、Safariがzstdを送るのは26.3以降。 - ヘッダが無いリクエストは「どのコーディングでも可」と解釈される(RFC 9110)。非圧縮だけを求めるなら
identityを明示する。 - curlの
--compressed、Pythonのrequests、Apache HttpClient 5は自動で解凍するが、Java標準のHttpClientはヘッダも付けず解凍もしない。 - Accept-Encodingによって表現を切り替えてキャッシュさせる場合は、原則として
Vary: Accept-Encodingを付ける。nginxは既定で付けない。
まず、Accept-Encodingで申告する方式と、Content-Encodingで通知される結果を区別します。
Accept-Encodingの役割とContent-Encoding・Acceptとの違い
リクエストとレスポンスでの対応関係
RFC 9110の12.5.3節によると、Accept-Encodingはユーザーエージェントがリクエストで送ると「レスポンスで受け入れられるコンテンツコーディング」を示します。サーバーは候補の中から1つを選んで本文を圧縮し、Content-Encoding: brのように使った方式を付けて返します。
GET /index.html HTTP/1.1
Host: example.com
Accept-Encoding: gzip, deflate, br, zstd
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Encoding: br
Vary: Accept-Encoding
Accept-Encodingで非圧縮が拒否されていなければ、サーバーは圧縮せずに返せます。そのときレスポンスにはContent-Encodingが付きません。
レスポンス側にAccept-Encodingが付くこともあります。RFC 9110は、リクエスト本文のコーディングに対応できないサーバーが415(Unsupported Media Type)を返すとき、受け付けられる方式をAccept-Encodingで知らせる使い方を「最も一般的な用途」として挙げています。
Content-EncodingとTransfer-Encodingの適用範囲の違い
Content-Encodingは表現(ファイルそのもの)にかかる符号化で、キャッシュされるのも圧縮済みの状態です。一方のTransfer-Encoding(chunkedなど)はHTTP/1.1の転送区間だけの符号化です。HTTP/2(RFC 9113)ではTransfer-Encodingを含むメッセージは不正として扱われます。ブラウザとの間で圧縮を使うのはContent-Encodingのほうです。
Accept系ヘッダの役割分担
Accept、Accept-Encoding、Accept-Languageは名前が似ていますが、指定する対象が異なります。どれもクライアントの希望を伝えるヘッダ(コンテンツネゴシエーション)ですが、対象が違います。
| リクエストヘッダ | 伝える内容 | 値の例 | サーバーの応答ヘッダ |
|---|---|---|---|
| Accept | メディアタイプ | text/html, application/json | Content-Type |
| Accept-Encoding | 圧縮方式 | gzip, br, zstd | Content-Encoding |
| Accept-Language | 言語 | ja, en;q=0.8 | Content-Language |
| Accept-Charset(非推奨) | 文字コード | utf-8 | Content-Typeのcharset |
JSONを返させたいのに圧縮の話をしている、という取り違えはこの表で防げます。形式の指定はAccept、圧縮の指定はAccept-Encodingです。
指定できる値:gzip・br・zstd・deflateとIANA登録一覧
使える値はIANAの「HTTP Content Coding Registry」で管理されています。2026年9月28日時点の登録から、実務で目にするものを抜き出しました。
| 値 | 形式 | 規定 | ブラウザの対応 |
|---|---|---|---|
| gzip | GZIP(RFC 1952) | RFC 9110 | 全ブラウザ |
| deflate | zlib形式のdeflate | RFC 9110 | 全ブラウザ |
| br | Brotli | RFC 7932 | Chrome 50・Firefox 44・Safari 11 |
| zstd | Zstandard(窓8MB以下) | RFC 9659・RFC 8878 | Chrome 123・Firefox 126・Safari 26.3 |
| dcb / dcz | 共有辞書つきBrotli / Zstandard | RFC 9842 | Chrome 130(Safariは未対応) |
| identity | 符号化なし(予約語) | RFC 9110 | 指定用の語 |
| compress | UNIX compress(LZW) | RFC 9110 | 主要ブラウザは送らない |
ブラウザ対応の版はMDNの互換性データ(browser-compat-data)の値です。x-gzip・x-compressは旧名として非推奨扱いになっています。
deflateを避ける理由
RFC 9110の定義では、deflateは「zlib形式で包んだdeflateデータ」です。ところが同じ節に、zlibのヘッダを付けずに生のdeflateデータを送る非準拠の実装があると注記されています。受け手が片方の形式しか読めないと解凍に失敗するため、サーバー側で新たにdeflateを出す理由はありません。gzipで足ります。
zstdの8MB窓と共有辞書(dcb・dcz)
IANAの登録ではzstdは「Window_Sizeが8MB以下」のストリームと定義されています(RFC 9659)。zstdの圧縮レベルを上げすぎて窓が8MBを超えると、仕様上のzstdコーディングではなくなり、ブラウザが解凍を拒否します。サーバーやCDNを使う場合も、zstdの出力がWindow_Sizeの上限8MBを満たす設定か確認してください。
dcbとdczは、以前に配信したファイルを辞書として差分だけを圧縮する方式(Compression Dictionary Transport)です。JavaScriptのバンドルを頻繁に更新するサイトで効きます。通常の設定で送ってくるのはChrome 130以降などのChromium系で、Firefoxは145以降で設定(network.http.dictionaries.enable)を有効にした場合に限られます(MDNの互換性データ)。
書式とq値・identity・ヘッダ無しの解釈ルール
q値で優先順位を付ける書き方
値はカンマ区切りで並べ、各値に;q=で0から1の重みを付けられます。重みを省略すると1です。並び順自体には優先度の意味はなく、比べるのはq値です。
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
1行目は「Brotliが最優先、次にgzip、それ以外も一応可」。2行目はRFC 9110の例で「gzipを優先、非圧縮も可、それ以外は不可」を意味します。*はヘッダに明示していないすべてのコーディングにマッチします。q=0は「受け付けない」の意味で、小数点はピリオドです(q=1,0のようなカンマは書式エラー)。
identity・空値・ヘッダ無しの違い
似ているようで、RFC 9110では扱いがはっきり分かれています。
| リクエスト | RFC 9110での意味 |
|---|---|
| ヘッダ無し | どのコーディングでも受け入れ可能 |
| Accept-Encoding:(空) | コーディングを使わないでほしい |
| Accept-Encoding: identity | 非圧縮を希望 |
| identity;q=0、またはidentityの個別指定がない場合の *;q=0 | 非圧縮を拒否 |
「ヘッダを付けなければ非圧縮で返ってくる」と書かれた解説は少なくありませんが、仕様上は逆で、ヘッダ無しは「何でもよい」です。実際にはnginxもApacheもAccept-Encodingに方式が載っていない限り圧縮しないため、多くの環境で非圧縮になるのは実装の慎重さによるものです。確実に非圧縮で受け取りたいクライアントはAccept-Encoding: identityを送ってください。
なお、どの圧縮方式も一致しなかった場合、サーバーは406を返すのではなく非圧縮で返すべき(SHOULD)とされています。例外はidentity;q=0などで非圧縮まで拒否されたときだけです。
ブラウザとHTTPクライアントが実際に送る値
主要ブラウザの既定値
MDNは、ブラウザのページ遷移で送られる典型値をgzip, deflate, br, zstdとしています。zstdが加わったのはChrome 123、Firefox 126からで、SafariはmacOS 26.3 Tahoeより前ではzstdを送りません。Edgeは同じChromiumなのでChromeと同じ値です。
この値は接続の種類でも変わります。Firefoxのソース(modules/libpref/init/all.js)では、平文のhttpで送るのはgzip, deflateだけで、httpsのときにbr, zstdが加わり、圧縮辞書機能が有効で、対象リクエストに使える辞書があればdcb・dczも付きます。ローカルのhttp環境でBrotliの動作を確かめようとしても、ブラウザがbrを要求しないので確認できません。
brやzstdを配信する場合も、zstdを送らないSafari 26.2以前などのクライアントにgzipで返せるよう、gzipの設定は残しておくのが安全です。
curl・Python・Node.js・Javaの既定値(実測)
2026年9月28日に、受け取ったヘッダを記録するだけのローカルサーバーへ各クライアントからリクエストを送り、Accept-Encodingの値を確かめました。
| クライアント | 既定で送る値 | 自動解凍 |
|---|---|---|
| curl 8.7.1(オプションなし) | 送らない | しない |
curl 8.7.1 --compressed |
deflate, gzip | する |
| Python urllib(3.9) | identity | しない |
| Python requests 2.32.5 | gzip, deflate | する |
| Node.js v26.5.0 fetch(http) | gzip, deflate | する |
| Node.js v26.5.0 fetch(https) | br, gzip, deflate, zstd | する |
| Java 25 java.net.http.HttpClient | 送らない | しない |
| Apache HttpClient 5.6.4 | gzip, deflate, x-gzip | する |
curlの--compressedが送る値は、curlがビルド時に組み込んだライブラリで決まります。上の8.7.1はBrotliとzstdなしでビルドされていたためdeflate, gzipだけでした。Node.jsのfetchは平文のhttpではbrとzstdを送らないので、ローカル開発環境と本番のhttpsで返る圧縮方式が変わる点に注意してください。
圧縮されているかを確認する手順
ブラウザの開発者ツールで見る場所
ChromeやFirefoxの開発者ツールでNetworkタブを開き、ページを再読み込みして対象のリクエストを選びます。Headersの「Response Headers」にcontent-encoding: brなどがあれば圧縮されています。一覧に出る転送サイズと展開後のサイズの差も、圧縮の効き具合の目安になります。
curlでヘッダと転送サイズを比べる
圧縮の有無と効果を数字で見るなら、-w '%{size_download}'で受信バイト数を出し、Accept-Encodingを変えて比べるのが確実です。
URL=https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/Accept-Encoding
for ae in identity gzip br zstd; do
curl -s -o /dev/null -D - -H "Accept-Encoding: $ae" \
-w "$ae: %{size_download} bytes\n" "$URL" | grep -i -E "content-encoding|bytes"
done
2026年9月28日にMDNの本ページで実行すると、identityが244,508バイト、gzipが30,377バイト、brが25,275バイトでした。gzipで約8分の1、Brotliはgzipよりさらに約17%小さくなっています。zstdを指定したリクエストは244,508バイトの非圧縮で返り、Content-Encodingも付きませんでした。対応しない方式を求められたサーバーが非圧縮で返す、前章のルールどおりの動きです。
-IはHEADリクエストを送るため、GETと違う応答を返すサーバーでは判断を誤ります。-o /dev/null -D -でGETのヘッダを見るほうが安全です。また--compressedで保存したヘッダには解凍後もContent-Encodingが残るとcurlのマニュアルに明記されています。
サーバー側の設定:nginxの既定値とVary
nginxのgzip設定で確認する5つの既定値
nginxのngx_http_gzip_moduleはgzip on;だけでは期待どおりに動かないことがあります。公式ドキュメントの既定値は次のとおりです。
| ディレクティブ | 既定値 | 困る場面 |
|---|---|---|
| gzip_types | text/html | CSS・JS・JSONが圧縮されない |
| gzip_vary | off | Vary: Accept-Encodingが付かない |
| gzip_proxied | off | CDN経由(Viaヘッダ付き)のリクエストを圧縮しない |
| gzip_comp_level | 1 | 圧縮率が最低 |
| gzip_min_length | 20 | 極小のレスポンスまで圧縮する |
特に見落としやすいのがgzip_proxied offです。nginxはViaヘッダの有無でプロキシ経由かを判定するため、Viaを付けるCDNの背後に置くと、オリジンで圧縮されずに届きます。CDN側で圧縮し直す設定がなければ、利用者には非圧縮のまま配信されます。
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 1024;
BrotliとzstdはNGINX公式のモジュール一覧に無く、使うにはサードパーティ製モジュール(ngx_brotliなど)を組み込むか、CDN側に任せることになります。Apache httpdはgzipをmod_deflateで、Brotliを2.4.26以降のmod_brotliで扱えます。
Vary: Accept-Encodingとキャッシュの取り違え
同じURLでもAccept-Encodingによって本文が変わるため、中間キャッシュには「このヘッダごとに別のキャッシュとして持て」と伝える必要があります。それがVary: Accept-Encodingです。これが無いと、Brotli版がキャッシュされた後にBrotli非対応のクライアントへ同じものが返り、文字化けや表示不能になります。Apacheのmod_deflateとmod_brotliは自動で付けますが、nginxは前述のとおりgzip_vary on;が必要です。
CDNのキャッシュキーの考え方はCDNの仕組みとキャッシュ制御、キャッシュの再検証はstale-while-revalidateとCache-Controlの再検証で扱っています。
Javaでの扱い:標準HttpClientは解凍しない
java.net.http.HttpClientでgzipを受け取るコード
Java 11以降のjava.net.http.HttpClientは、Accept-Encodingを自動では付けません。自分でヘッダを付け、サーバーがgzipを選択して返した場合も、クライアントは解凍しないため、そのまま文字列にすると壊れたバイト列になります。前述のローカルサーバー(1,200バイトの本文を返す)で試すと、36バイトのgzipデータのまま渡されました。Content-Encodingを見て自分でGZIPInputStreamを被せます。
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPInputStream;
public class GzipFetch {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder(URI.create(args[0]))
.header("Accept-Encoding", "gzip")
.build();
HttpResponse<InputStream> res =
client.send(req, HttpResponse.BodyHandlers.ofInputStream());
int status = res.statusCode();
if (req.method().equals("HEAD") || status == 204 || status == 304) {
res.body().close();
System.out.println(status + " / no body");
return;
}
String enc = res.headers().firstValue("Content-Encoding").orElse("identity");
try (InputStream in = decode(res.body(), enc)) {
String text = new String(in.readAllBytes(), StandardCharsets.UTF_8);
System.out.println(enc + " / " + text.length() + " chars");
}
}
static InputStream decode(InputStream raw, String enc) throws IOException {
try {
if (enc.equalsIgnoreCase("gzip")) {
return new GZIPInputStream(raw);
}
if (enc.equalsIgnoreCase("identity")) {
return raw;
}
throw new IOException("unsupported Content-Encoding: " + enc);
} catch (IOException e) {
raw.close();
throw e;
}
}
}
Java 25(25.0.4.1)で同じサーバーに送るとgzip / 1200 charsと出力され、正しく解凍できました。Accept-Encodingに書くのは、実際に解凍処理を持っている方式だけにしてください。解凍できないbrまで並べると、サーバーがBrotliを選んだ時点で例外になります。
もう1つの落とし穴はHEADリクエストや204・304の応答です。本文が空でもContent-Encoding: gzipが付くことがあり、分岐を入れる前のコードでHEADを送ったところGZIPInputStreamの生成時にEOFExceptionが発生しました。上のコードはHEAD・204・304を先に判定して解凍を通しません(HEADを送るように書き換えて試すと200 / no bodyと出力されました)。解凍の準備で例外が出たときも受信ストリームを閉じるよう、decodeに分けています。
Apache HttpClient 5とSpringのRestClient
Apache HttpClient 5(5.6.4で確認)は、リクエストにAccept-Encodingが無ければgzip, deflate, x-gzipを自動で付け、応答も自動で解凍します。Brotliなどは、対応するデコーダのライブラリがクラスパスにあるときだけ候補に加わる実装です。
SpringのRestClientやRestTemplateが解凍するかどうかは、下層に使うHTTPクライアントで決まります。Apache HttpClient 5を下層にすれば自動解凍が効きます。下層の選び方はSpringのRestClientの使い方で解説しています。
圧縮しないほうがよい場面
圧縮は基本的に有効にすべきですが、次の3つのケースでは外すか対策を加えます。
1つ目はBREACH攻撃(CVE-2013-3587)の条件がそろうレスポンスです。発見者のサイトによると、成立条件は「HTTPレベルで圧縮している」「ユーザー入力を本文に反映する」「CSRFトークンなどの秘密を本文に含む」の3つです。攻撃者は暗号化されたレスポンスのサイズ変化から秘密を1文字ずつ推測します。フォームや管理画面など3条件がそろうページでは、トークンをリクエストごとにランダム化するかマスクする、あるいはそのレスポンスだけ圧縮を切ります。静的なCSSやJSにこの心配はありません。
2つ目はJPEG・PNG・WebP・MP4・ZIP・PDFなど、すでに圧縮されている形式です。再圧縮してもほとんど縮まず、CPUを使うだけです。nginxのgzip_typesにこれらを入れないでください。
3つ目は数百バイト程度の小さなレスポンスです。gzip形式はヘッダ10バイトとトレーラ8バイトを必ず持つため(RFC 1952)、数百バイトの応答では縮む量がわずかです。nginxの既定値20バイトではほぼすべての応答が圧縮対象になるため、実際の応答サイズの分布を見て閾値を上げます(上の設定例では1024バイト)。
Accept-Encodingに関するよくある質問
accept-encoding: identity とはどういう意味ですか?
「圧縮せずにそのまま送ってほしい」という指定です。identityはRFC 9110で予約された語で、符号化なしを表します。Python標準のurllibは既定でこの値を送ります。逆にidentity;q=0と書くと非圧縮での応答も拒否することになり、どの方式も合わなければサーバーはエラーを返すしかなくなるため、通常は使いません。
gzip, deflate, br, zstd はそれぞれどう違いますか?
gzipは最も互換性が高い標準的な方式、brはGoogleが開発したBrotliでgzipより小さくなり、zstdはMeta(旧Facebook)が開発したZstandardで圧縮と解凍が速いのが特徴です。deflateはgzipと同系統ですが実装差があり、新規に使う理由はありません。ブラウザがこの4つを並べて送り、サーバーが対応するものから1つを選びます。
Accept-Encodingを指定しないとどうなりますか?
RFC 9110ではヘッダ無しは「どのコーディングでも受け入れ可能」と解釈されます。ただしnginxやApacheはAccept-Encodingに載った方式しか使わないため、実際には非圧縮で返ることがほとんどです。Java標準のHttpClientやオプションなしのcurlはヘッダを送らないので、転送量を減らしたいなら明示的に付けて、解凍処理も用意してください。
curlで圧縮されたレスポンスを確認するには?
curl -s -o /dev/null -D - -H "Accept-Encoding: gzip" URLでレスポンスヘッダを表示し、Content-Encoding: gzipがあれば圧縮されています。本文も読みたいときは--compressedを付けると自動で解凍されます。-w '%{size_download}'で受信バイト数を出し、identityと比べると圧縮率が分かります。
Transfer-EncodingとContent-Encodingはどう違いますか?
Content-Encodingはファイル自体を圧縮した状態を示し、その形のままキャッシュされます。Transfer-EncodingはHTTP/1.1で転送するときだけの符号化で、主にchunked(分割転送)に使われます。HTTP/2以降では使われません。HTTP/3の土台であるQUICについてはQUICとHTTP/3の仕組みを参照してください。