nginx で sub_filter_types “*” を使う前に知っておくべき仕様
はじめに
nginx では sub_filter 機能を使うことで、レスポンスの文字列を別の文字列に置き換えることができます。 特に WordPress のドメイン差し替えや、未知の JavaScript / API レスポンスを強制的に書き換えたいときなど、sub_filter_types “*”; を指定すれば「すべてのレスポンスを対象」にできるため、とても便利に見えます。 しかし、この指定には公式ドキュメントには明記されていない問題があり、そのまま運用すると環境によっては動画や音声ファイルの配信がうまくいかなくなる可能性があります。
本記事では、sub_filter_types “*” 、すなわち全てのレスポンスに sub_filter を適用する際に知っておくべき仕様と、その回避策についてまとめます。
sub_filter の仕様と副作用
公式ドキュメントには詳細な注意書きはありませんが、sub_filter モジュールは「レスポンスを指定した文字列で置換するフィルタ」として動作します。
この仕組みによりレスポンス内容が動的に変更される可能性があるため、元の Content-Length ヘッダーは正確ではなくなります。nginx はこのような場合、自動的に Content-Length を削除し、代わりにチャンク転送エンコーディング(Transfer-Encoding: chunked)を付与します。
また、sub_filter を適用するレスポンスは sub_filter_types で指定可能ですが、ワイルドカードの * を指定すると全ての MIME タイプが対象になります。 そのため、テキストだけでなくバイナリファイルもフィルタ処理の対象に入り、実際には置換されなくても Content-Length が外れてしまいます。
Range リクエストが効かなくなる問題
動画ファイルなどにアクセスした場合、nginx はデフォルトで Range リクエストに対応しており、適切なリクエストに対しては 206 レスポンスを返します。
ところが、sub_filter が有効になっている場合は Range リクエストに対応できず、常に 200 を返してしまいます。 これは置換によってレスポンスの長さが固定できないため、Content-Length を返せず Range に対応できないのが原因です。
その結果、クライアント側では動画や音声のシークができなくなり、毎回ファイル全体をダウンロードする必要が生じます。 特に大容量ファイルでは再生開始が遅くなったり、ネットワーク負荷や転送量が増えたりするなど、ユーザー体験や配信効率にデメリットがあります。
解決策
sub_filter_types は location ディレクティブ内でも再定義できます。
そのため、たとえより上のレベルで sub_filter_types “*”; を指定していても、静的ファイルを返す location にて sub_filter の設定を上書きすることでこの問題を回避可能です。
# 全体では sub_filter を有効化 sub_filter_types "*"; sub_filter 'https://old.example.com' 'https://new.example.com'; # 静的バイナリファイルの location では無効化 location ~* \.(jpe?g|png|gif|webp|mp4|mov)$ { sub_filter_types "text/dummy"; # 実在しない MIME type を指定して無効化 }
ここで指定している text/dummy は実際には存在しない MIME type であり、これによりその location 内ではフィルタ対象がゼロになり、結果的に sub_filter がオフになります。
もちろん、本当に必要な部分のみに sub_filter_types “*”; を指定する方法でも大丈夫なので、適切な方法を利用してください。
まとめ
nginx で sub_filter_types “*” を使う前に知っておくべき仕様は以下の通りです。
- “*” を指定した場合、テキストファイルだけでなくバイナリファイルもレスポンスの置換対象とみなされる
- sub_filter はレスポンスを書き換える可能性があるため、Content-Length が返らず Transfer-Encoding: chunked になる
- その結果、Range リクエスト (206) が無効になり、動画や音声のシークや部分取得ができなくなる
- トップレベルで sub_filter_types “*”; を利用している場合は、静的ファイルの配信用 location 内で以下のように記載し、フィルタを実質的に無効化するのが解決策
これらの挙動は公式ドキュメントに明確な記載はありませんが、実際に動作を確認できました。
仕組みを理解すれば納得できる仕様です。