gzip、Brotli、Zstd content-encoding: identity を誤って設定した開発者のための HTTP 壊し方
HTTP圧縮交渉の仕組み(Accept-Encoding / Content-Encoding)、gzip、Brotli、Zstd の並列ベンチマーク、curl で圧縮が実際に動作しているかを確認する方法、および4つの誤設定が静かにそれを無効化する方法を説明します。
あなたの nginx 設定には gzip on;があります。アプリは JSON を返します。レスポンスボディはまだ圧縮されていない状態で 35KB です。エラーも警告もありません — 壊れていませんが、圧縮が静かに実行されていません。
これは通常、4つの誤設定のいずれかです。しかしまず、交渉が実際にどのように機能するかを確認します。
HTTP 壊れ圧縮交渉の仕組み
2つのヘッダー、それ以外は一切ありません。クライアントは、どのアルゴリズムを解圧できるかを Accept-Encodingに発表します。サーバーはアルゴリズムを選択し、ボディを圧縮し、その選択を Content-Encoding:
GET /api/data HTTP/1.1
Host: example.com
Accept-Encoding: gzip, deflate, br, zstd
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: br
Vary: Accept-Encoding
の Vary: Accept-Encoding ヘッダーは、CDN キャッシュの正確性を気にする場合に必須です。このヘッダーがないと、CDN は Brotli 圧縮されたレスポンスをキャッシュし、クライアントが only advertised gzip で Accept-Encodingにしか発表していない場合、そのクライアントは Brotli を gzip として解圧しようとし、ゴミを取得します。nginx の gzip_vary on; はこれを自動的に追加します。
Content-Encoding: identity は技術的に有効ですが、「圧縮なし」という意味です — しかし誰も明示的に設定しません。実際の失敗モードは逆です:あなたが期待するはずの場所に Content-Encoding ヘッダーがまったく存在しません。
圧縮が実際に機能しているかを確認する
デバッグ設定を行う前に、問題を確認します。
# Check headers only
curl -sI -H "Accept-Encoding: gzip, br, zstd" https://example.com/api/data | grep -i "content-encoding\|vary"
# Compare compressed vs uncompressed size
curl -so /dev/null -w "uncompressed: %{size_download} bytes
" https://example.com/api/data
curl -so /dev/null --compressed -w "compressed: %{size_download} bytes
" https://example.com/api/data
--compressed を送信し、レスポンスを自動的に解圧します。両方の数字が一致する場合、圧縮が実行されていません。すべてのレスポンスヘッダーと、それぞれのヘッダーが文脈で意味を持つように確認したい場合は、 Accept-Encoding: deflate, gzip, br, zstd はそれらを注釈付けし、 HTTPヘッダーアナライザー 、および cache-control ディレクティブを含みます。 Vary, Content-Encodinggzip と Brotli と Zstd
今日の HTTP で実際に関連性があるアルゴリズムは3つです。以下のベンチマークデータは、
Zstd 官方ベンチマーク の Silesia コルパス — 混合された現実のファイル(HTML、ソースコード、PDF、データベース)を標準データセットとして、Core i7-9700K でテストされたものです。純粋な JSON またはプレーンテキストのペイロードは通常、圧縮がよく効果的です。 gzip
| アルゴリズム | レベル | 比率 | 圧縮 | 展開 |
|---|---|---|---|---|
| 1(高速) | 2.74x | 69 MB/s | 380 MB/s | 380 MB/s |
| 1(高速) | 6(デフォルト) | 2.97x | 29.9 MB/s | 360 MB/s |
| 1(高速) | 9(最大) | 3.10x | 18 MB/s | 360 MB/s |
| Brotli | 4 | 3.18x | 104 MB/s | 440 MB/s |
| Brotli | 11(最大) | 3.74x | 0.4 MB/s | 440 MB/s |
| Zstd | 2.74x | 2.88x | 430 MB/s | 1,380 MB/s |
| Zstd | 3(デフォルト) | 3.01x | 320 MB/s | 1,350 MB/s |
| Zstd | 19(最大) | 3.40x | 17.5 MB/s | 1,380 MB/s |
1(高速) は基準です。レベル6は即時処理の正しい選択です — レベル6からレベル9に移行する場合、CPU 使用量が65%増加し、得られる比率が約4%改善しますが、動的応答には価値がありません。事前に圧縮された静的ファイルは別の計算です。
Brotli は、同等のCPUコストでレベル4-6においてgzipを真正に上回り、解圧速度が約20%速くなります。理由は、Brotliはウェブコンテンツに最適化された静的辞書を持っているため — HTMLエンティティ、HTTPフィールド名、JavaScriptキーワードです。同じ素材に対して一般用途の圧縮器よりも高い比率を達成します。レベル11は、事前に圧縮された静的資産にのみ適用可能です。圧縮速度が0.4 MB/sであるため、約25MB/分の圧縮が可能です。これはビルドステップであり、リクエストハンドラーではありません。
Zstd は速度に関する話です。デフォルトレベル(3)はgzipの比率を達成していますが、圧縮速度が10倍速く、解圧速度がほぼ4倍速くなります。主な制限はブラウザのサポートです:Chrome 118以降(2023年10月)、Firefox 126以降(2024年5月)、Safari 18以降(2024年末)。まだ全ブラウザでサポートされていないため、単一アルゴリズムとして使用することはできませんが、サーバーが適切に交渉している場合、Zstdを追加すると数行の設定を必要とし、Brotliを宣言するクライアントに役立ちます。レベル19のZstdは、Brotli-11の比率に近づきながら、圧縮速度の深刻なペナルティを回避し、高圧縮要件の即時処理により利用可能になります。
ブラウザおよびクライアントのサポート
| アルゴリズム | Chrome | Firefox | Safari | Edge | Node.js |
|---|---|---|---|---|---|
| 1(高速) | すべて | すべて | すべて | すべて | 組み込み(zlib) |
| deflate | すべて | すべて | すべて | すべて | 組み込み(zlib) |
| Brotli (br) | 51+ | 44+ | 11+ | 15+ | v10.16+ |
| Zstd | 118+ | 126+ | 18+ | 118+ | v21+ |
重要な特徴: br と zstd はHTTPS接続でのみ表示されます。ブラウザは、明確なHTTP接続ではそれを発表しない — MITM攻撃によるエンコーディングヘッダーの注入を防ぐためです。テスト中に Accept-Encoding で、なぜだけ http://localhost が表示されないのか、その理由です。HTTPSでテストするか、curlを直接使用してください(curlはこの制限を適用しません)。 gzip, deflate4つの静かに破綻する誤設定
1. gzip_proxied が欠落(nginx リバースプロキシ)
nginx の
モジュールは、nginxが自ら生成するレスポンスを圧縮します。プロキシリクエスト(上流アプリからnginxに、クライアントへ)の場合、あなたは gzip が必要です — そうでなければ、nginxは自らのコンテンツハンドラーからのレスポンスのみを圧縮し、上流からのレスポンスは圧縮しません。 gzip_proxied ほとんどのnginx設定はリバースプロキシです。ほとんどのチュートリアルは proxy_pass を省略しています。これらの2つの事実が、多くの静かに圧縮されていないレスポンスを説明しています。
# This is NOT enough when nginx is a reverse proxy:
gzip on;
gzip_types text/plain application/json application/javascript text/css;
# You need this too:
gzip_proxied any;
2. MIMEタイプが gzip_types に含まれていない gzip_proxiednginx のデフォルト
のみです。JSON、CSS、JavaScript、SVG — すべてが明示的にリストされていない限り、圧縮されません:
nginx はベース MIME タイプに基づいてマッチしますので、 gzip_types は text/html は
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/rss+xml
image/svg+xml;
をカバーします。charsetのバリエーションを別々にリストする必要はありません。 application/json 3. 中間プロキシが Accept-Encoding を削除 application/json; charset=utf-8AWS ALB、誤設定された Cloudflare Workers、および一部の API ゲートウェイ設定は、
を削除または変更します。サーバーはこのヘッダーを確認せず、デフォルトで圧縮を無効にし、すべてのダウンストリームのシステムが機能が壊れていると誤認します。チェーンのどこにもエラーは表示されません。
デバッグには、オリジンレスポンスとCDNレスポンスを比較します: Accept-Encoding オリジンが
を直接返すが、CDNレスポンスには
# Via CDN/proxy
curl -sI -H "Accept-Encoding: gzip, br" https://example.com/api/data
# Direct to origin (bypassing CDN via --resolve or direct IP)
curl -sI -H "Accept-Encoding: gzip, br" --resolve "example.com:443:ORIGIN_IP" https://example.com/api/data
が存在しない場合、CDNが何かを削除している可能性があります — おそらく、 Content-Encoding: gzip をインバウンドで削除しているため、オリジンが最初から圧縮されないのです。 Content-Encoding4. 上流アプリが圧縮し、nginxが再度圧縮を試みる Accept-Encoding あなたの Node.js/Go/Python アプリがレスポンスボディを圧縮し、
を設定している場合、nginx は二重圧縮を回避すべきですが、これはヘッダーのタイミングに依存します。上流がストリーム中でヘッダーを送信するか、nginx の検出が競合する場合、クライアントが解圧できない二重圧縮されたゴミが得られます。
クリーンな解決策:nginx がすべての圧縮を所有するようにします。アプリ内の圧縮ミドルウェア(express の Content-Encoding: gzipモジュール、Go の
、など)を削除し、プレーンレスポンスを返し、nginx がエッジで圧縮を行うようにします。同じパフォーマンスの向上を得ることができ、二重圧縮のリスクがありません。 compression 機能する設定 gzip.Handlernginx
Apache
Caddy
gzip on;
gzip_vary on; # adds Vary: Accept-Encoding automatically
gzip_proxied any; # compress responses from proxied upstreams
gzip_comp_level 6;
gzip_min_length 256; # skip tiny responses where overhead isn't worth it
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/rss+xml
image/svg+xml;
# Brotli requires the ngx_brotli module
# https://github.com/google/ngx_brotli
brotli on;
brotli_comp_level 4;
brotli_static on; # serve pre-compressed .br files when they exist
brotli_types
text/plain
text/css
application/json
application/javascript
image/svg+xml;
Caddy は gzip および Brotli をデフォルトで有効にします。Zstd を明示的に追加するには:
LoadModule deflate_module modules/mod_deflate.so
AddOutputFilterByType DEFLATE text/html text/plain text/css application/json application/javascript image/svg+xml
Header append Vary Accept-Encoding
# mod_brotli requires Apache 2.4.26+
LoadModule brotli_module modules/mod_brotli.so
AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css application/json application/javascript image/svg+xml
MIMEタイプリスト、または
のエッジケース、正しい
example.com {
encode gzip zstd br
reverse_proxy localhost:3000
}
の処理がデフォルトで行われます。『どのサーバーが圧縮関連の誤設定面積が最も少ないか』という質問への誠実な答えは Caddy です。 gzip_proxied 自分のペイロードで圧縮をテストする Vary Silesia コルパスのベンチマークデータは相対的なパフォーマンスを示しますが、あなたの特定のペイロードがより重要です。再現性のあるフィールド名を持つJSON APIレスポンスは、minified JavaScriptや混合HTMLと異なります。これらのツールは、ローカルの圧縮サーバーを起動せずに、ブラウザ内で特定のペイロードをテストできます:
— ペイロードを貼り付け、即座に圧縮サイズと比率を確認
— Brotli圧縮を異なる品質レベルでテスト
- Gzip / Zlib / Deflate テスター — ブラウザ内でZstdをエンコード/デコード
- ブロトリー圧縮エンコーダー/デコーダー Brotli-11で静的資産を事前に圧縮するか、nginxが即時でgzipを処理するかを決定する際に役立ちます。実際のレスポンスペイロードを貼り付け、比率を比較し、実際の数字に基づいて判断します。
- Zstandard(Zstd)圧縮ツール レスポンスが圧縮されていない場合、
が圧縮なしを確認している場合、解決策は上記の4つの誤設定のいずれかです — 最も可能性が高いのは
まとめ
のnginx、またはCDNがあなたの curl -sI ヘッダーを食い尽くしていることです。直接オリジンを確認し、サーバー設定を責める前に確認してください。 Content-Encodingアルゴリズムの選択に関して:動的APIレスポンスにはgzip-6が十分であり、設定リスクはほとんどありません。静的資産にはBrotliを追加してください — ビルドステップでレベル11で事前に圧縮し、 gzip_proxied any; を設定し、Brotliを宣言しないクライアントにはgzipに自動的に切り替えるようにします。Zstdは今すぐ追加すべきです;設定コストは非常に小さく、ブラウザのフィートポールが急速に成長しています。すべてのアルゴリズムを正しく Accept-Encoding 処理できるようにすることは、新しいシステムにとって適切な姿勢です。
gzip、Brotli、Zstd:HTTP圧縮、コンテンツ-エンコーディング:identityを誤って設定した開発者向け2 brotli_static ongzip、Brotli、Zstd:HTTP圧縮、コンテンツ-エンコーディング:identityを誤って設定した開発者向け1 br。Zstdは今、追加すべきです。設定コストは微小であり、ブラウザのサイズは急速に増加しています。正しい処理を提供することは、新しいものの正しい姿です。 Vary: Accept-Encoding 処理は、新しいものの正しい姿です。
恵 スコアボードが到着しました!
スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!
