WebサイトのHTTP圧縮検証|Content-Encoding・Vary・二重圧縮の不整合を見つける方法
gzipやBrotliを有効にした後、一部のブラウザだけでページを表示できない、CDN経由で取得するとファイルが壊れる、圧縮設定を変えても転送量が変わらない。このような症状では、圧縮の有効・無効だけを見ても原因を特定できません。クライアントが許容した形式、サーバーが宣言した形式、実際に届いたデータを一組で調べる必要があります。
HTTP圧縮は、Accept-Encodingを変えて取得した応答のContent-Encodingと本文を照合し、宣言どおりに復号した内容が期待するファイルと一致するかで検証します。そのうえでVaryとキャッシュの状態を確認し、CDN経由とオリジンのどちらで差が生まれるかを切り分けます。圧縮の検証は、転送量ではなく復号後の内容まで一致して初めて完了します。
対象は、自社で管理するHTML・CSS・JavaScriptなどのWeb配信です。配信基盤全体の構成を整理したい場合は、Webサイト構築の基盤選びも確認してください。以下の仕様説明は2026年9月21日時点のRFC 9110、curl、Cloudflareの公式情報に基づきます。
本記事のポイント
- 圧縮の合否は、要求条件とContent-Encodingを照合し、宣言された方式で復号した本文が期待する内容に戻るかで判断する。
- curlの--compressedは本文を自動展開する一方で保存ヘッダーを変更しないため、圧縮データの検査と利用者視点の確認を分ける。
- Varyとキャッシュ状態を記録し、CDN経由とオリジンを比較して、圧縮・展開・ヘッダー設定のどの段階に不整合があるかを絞る。
最初に要求・応答・本文の3点を分ける
Accept-Encodingは、クライアントが受け入れるコンテンツ符号化方式を伝える要求ヘッダーです。Content-Encodingは、実際に応答へ適用した符号化方式を伝えます。Varyは、応答を選ぶ際に影響した要求ヘッダーをキャッシュへ知らせます。同じURLでも圧縮方式が異なる応答を扱うため、3つを別々の役割として読みます。
| 確認対象 | 分かること | それだけでは分からないこと |
|---|---|---|
| Accept-Encoding | 要求で許容した圧縮形式と優先度 | 実際に採用された形式 |
| Content-Encoding | サーバーが宣言した符号化方式 | 本文が宣言どおりのデータか |
| Vary | キャッシュの応答選択に関係する要求ヘッダー | CDN独自の正規化や内部キャッシュキーの全仕様 |
| 復号後の本文 | 期待する内容に戻るか | どの配信段階が不整合を作ったか |
RFC 9110では、Accept-Encodingがない要求と、値が空の要求は同じ意味ではありません。ヘッダーがなければ任意の符号化方式が許容される扱いで、空の値は符号化を望まないことを示します。非圧縮を明示して比較するならidentityを使います。また、gzipを指定しても、非圧縮を拒否していなければ非圧縮応答が直ちに異常とは限りません。q=0はその方式を受け入れない指定です。
Content-Encodingがgzipならgzipとして復号し、brならBrotliとして復号します。非圧縮の応答では通常Content-Encodingを付けません。identityはAccept-Encodingで特別な意味を持つ値であり、応答にContent-Encoding: identityを付ける運用を標準にしないでください。

同じファイルを3条件で取得して比較する
1. 内容が固定された検証対象を選ぶ
最初は、更新日時やユーザーごとの表示が混ざらない静的ファイルを選びます。ログイン状態、Cookie、言語、地域、A/Bテストによって本文が変わるページを使うと、圧縮の不具合と正常な内容差を区別しにくくなります。対象URL、実行時刻、curlのバージョン、要求ヘッダー、CDN経由かどうかを記録してください。
比較中にファイルを更新しないことも重要です。HTMLなら埋め込まれた時刻やnonce、外部サービスから取得した動的データを確認します。固定できない場合は、本文の完全一致だけで判定せず、同じ版の静的アセットで先に圧縮経路を検証します。ブラウザの見た目が同じでも、バイト列が同一とは限りません。
2. 自動展開を使わずに応答を保存する
次の例は、自分が管理するURLへ置き換えて使います。curlの既定動作で転送上のチャンク処理を行いながら、Content-Encodingの圧縮データを保存するため、--compressedも--rawも付けません。リダイレクトを自動追跡する指定も外し、まず対象URLが直接返す応答を確認します。
curl -sS --max-time 30 -H 'Accept-Encoding: identity' -D identity.headers -o identity.body 'https://example.com/assets/site.css'
curl -sS --max-time 30 -H 'Accept-Encoding: gzip' -D gzip.headers -o gzip.body 'https://example.com/assets/site.css'
curl -sS --max-time 30 -H 'Accept-Encoding: br' -D br.headers -o br.body 'https://example.com/assets/site.css'
通信が成功しても、保存された本文が目的のファイルとは限りません。先にHTTPステータスとContent-Typeを確認します。301や302なら正しい移動先を確認して同じ取得をやり直し、403なら認証やアクセス制限、404ならURLを調べます。エラーページを正常なCSSと比較して「圧縮で内容が変わった」と判断しないようにします。
3. 宣言された方式に従って復号する
ファイル名は要求条件のラベルであり、データ形式を保証しません。gzip.bodyでもContent-Encodingがなければ非圧縮として扱います。Content-Encodingがgzipと確認できた場合だけ、次のように復号します。Brotliの例には別途brotliコマンドが必要です。未導入の場合は、その環境で利用できる信頼できる復号ツールを用意してから検証します。
gzip -dc gzip.body > gzip.decoded
brotli -d -c br.body > br.decoded
shasum -a 256 identity.body gzip.decoded br.decoded
3ファイルが同じ固定コンテンツであれば、復号後のハッシュは一致することが期待されます。圧縮後のgzipとBrotliのバイト列やハッシュが異なることは正常です。gzipでも生成条件により圧縮済みデータは変わり得ます。比較する対象を「復号後の本文」にそろえてください。コマンドの終了コードが失敗なら、空ファイルや途中までの出力を合格扱いしません。
curl --compressedは、対応する方式の圧縮応答を要求し、受け取った本文を自動で復号します。一方、保存した応答ヘッダーは書き換えません。そのため「Content-Encoding: gzipなのに保存ファイルが平文」という状態が、curl自身の正常な展開によって生まれます。利用者として読めるかを見る検証と、圧縮されたデータを調べる検証でコマンドを分けましょう。
--rawは内容符号化だけでなく転送符号化の内部デコードも無効にします。HTTP/1.1でchunked転送されたデータにはチャンク境界が残り、そのままgzipの入力にすると失敗する場合があります。「生データを保存したいから常に--raw」という運用は避け、今回の検証では上記の手動Accept-Encoding指定を使います。
VaryとCDNの配信段階を切り分ける
キャッシュ可能な応答をAccept-Encodingによって選び分ける場合、Vary: Accept-Encodingが検討対象になります。RFC 9110では、Varyに挙げた要求ヘッダーの条件を満たさない後続要求へ、その保存応答をそのまま使い回してはいけません。ただしVaryの有無だけで、CDNの内部実装まで断定することはできません。提供元の正規化・圧縮・キャッシュキーの仕様と、実際の応答を一緒に確認します。
同じURLに対してidentity、gzip、brの順で取得し、次に順序を逆にして比較します。キャッシュへの格納順によって、不対応の圧縮データが返ったり、本文とContent-Encodingが食い違ったりしないかを見ます。CDNが提供するキャッシュ状態ヘッダーやAgeも記録します。HITとMISSで結果が異なる場合は、キャッシュの選択や変換処理が調査候補になります。
キャッシュを避けるためにURLへランダムなクエリを追加すると、キャッシュキーやアプリの処理自体が変わることがあります。通常利用者と同じURLでの再現を残したうえで、承認された検証環境や対象を限定した無効化手順を使います。キャッシュ削除の影響範囲は、Cloudflare Cache RulesとPurgeの運用も参照してください。
Cloudflareの公式資料は、訪問者とCloudflareの間、Cloudflareとオリジンの間を別の圧縮経路として説明しています。オリジンへの要求ではbrとgzipを受け入れ、訪問者へは対応形式やルールなどに応じて配信します。したがって、ブラウザが送ったAccept-Encodingがそのままオリジンへ届くとは限りません。オリジンのログだけで、最終利用者が受け取った圧縮形式を判断しないでください。
オリジンを直接調べる場合は、管理者が許可した方法でHost名とTLSの検証条件をそろえます。保護されたオリジンを検証のために一般公開する必要はありません。CDN経由が失敗し直接応答が正常ならCDN側の変換とキャッシュを、両方が失敗するならオリジン側の生成・圧縮・ヘッダー設定を優先して調べます。
二重圧縮と長さの不整合を直す順番
Content-Encodingは、符号化を適用した順に方式を列挙します。たとえばgzipの後にbrを適用したならgzip, brと宣言し、受信側は逆順に復号します。複数の符号化そのものが仕様違反なのではありません。問題になるのは、二度処理したのに一度分しか宣言しない、すでに展開した本文へ古いContent-Encodingを残すなど、宣言と実体が合わない状態です。
| 観測した症状 | 先に調べること | 修正後の確認 |
|---|---|---|
| gzipと宣言されているのに復号できない | 自動展開済みではないか、本文がエラーページではないか、転送データが混ざっていないか | 圧縮データを保存し直して復号する |
| 一度復号しても圧縮データが残る | 複数段の処理とContent-Encodingの列挙順 | 意図した回数の復号で本文に戻るか |
| CDNのHIT時だけ失敗する | キャッシュの応答選択、格納時と返却時の変換 | 要求順序を変えて再取得する |
| Content-Lengthとファイルサイズが違う | 圧縮前後のどちらを測ったか、途中切断、自動展開、動的変換 | 同じ段階のバイト数を比べる |
Cloudflareは、動的変換で長さが変わる場合などにContent-Lengthを省略することがあります。ヘッダーがないことだけで転送失敗とは判断しません。公式資料では、オリジンのCache-Control: no-transformが圧縮変更を防ぎ、元のContent-Lengthを維持するための条件として説明されています。ただし圧縮や他の変換を制限する指定なので、長さを表示するためだけに一律追加せず、配信機能への影響を確認します。クライアント要求へ付けても、同じ設定にはなりません。
修正は、アプリ、Webサーバー、リバースプロキシ、CDNのどこが圧縮とヘッダーを担当するかを整理してから行います。疑わしい設定を同時に複数変えると原因が消えても責任箇所が分かりません。検証環境で一段ずつ変更し、復号成功、本文一致、非対応形式の不送信を確認した後に対象を限定して反映します。
完了記録には、変更前後の設定、対象URL、要求条件、応答ヘッダー、復号ツールと終了結果、本文ハッシュ、切り戻し条件を残します。実装担当と運用担当の境界が曖昧なら、Webサイトの更新責任と承認の決め方に沿って担当を定めてください。圧縮設定を有効にした時点ではなく、実際の利用経路で確認した時点を完了にします。
よくある質問
gzipを要求したのに非圧縮で返るのは異常ですか?
必ずしも異常ではありません。identityを拒否していなければ、非圧縮応答が許容される場合があります。コンテンツの種類、サイズ、ステータス、CDNルールも確認し、要求した方式と実際の採用方式を分けて記録します。
Accept-Encodingを送らなければ非圧縮になりますか?
仕様上、その保証はありません。ヘッダーなしは任意の符号化を許容する扱いであり、空の値とは異なります。非圧縮の比較条件はidentityを明示して作ります。
--compressedで保存した本文をもう一度gzipで展開しますか?
通常は不要です。curlがすでに復号しています。保存したヘッダーにはContent-Encodingが残るため、コマンドのオプションと本文を一緒に確認してください。
gzipとBrotliのハッシュが違ってもよいですか?
圧縮済みデータのハッシュが異なるのは正常です。同じ固定ファイルを配信しているかは、宣言された方式で復号した後の内容で比較します。
二重圧縮は必ずHTTP仕様違反ですか?
複数の符号化は仕様で表現できます。適用した順序とContent-Encodingが一致し、受信側が復号できる必要があります。不要な重複処理は避けつつ、まず宣言と実体の不整合を調べます。
Content-Lengthがない応答は不完全ですか?
それだけでは判断できません。HTTPの転送方法やCDNの変換によって省略される場合があります。通信完了、復号の成否、期待する本文との一致を確認します。
CDNとオリジンの設定が分散し、表示不具合の原因を追いにくい場合は、配信経路と検証手順を整理することから始められます。Web基盤の設計・実装や運用改善について、ファネルAiへご相談ください。