nginxのlocationの優先順位を誤解していた

最近nginxを触っていて、locationの優先順位、主に^~の有無による前方一致の扱いについて誤解していたことに気づいた。

まず正解から。locationは以下の流れで決定される

  1. 完全一致チェック -> location = path {}
    • locationとURIが完全一致すれば、この設定で確定
  2. 前方一致チェック -> location path {} または location ^~ path {}
    • ^~ の有無や記述の順番は優先度に影響しない。最長一致するものが選ばれる
      • ^~ ありのlocationが選ばれた場合、この設定で確定
      • ^~ なしのlocationが選ばれた場合、保留して正規表現チェックに移る
  3. 正規表現チェック -> location ~* regexp または location ~ regexp {}
    • 設定ファイルに記述された順番でマッチングしていく
      • マッチするものがあれば、その設定で確定
      • マッチするものがなければ、保留していた前方一致の設定を使う

例えばこんな設定をしたとして

        location /hoge/ {
            return 403;
        }
        location ^~ /hoge/images/ {
            root /var/www/html;
        }
        location ^~ /fuga/ {
            return 403;
        }
        location /fuga/images/ {
            root /var/www/html;
        }
        location ~ \.jpg$ {
            root /data/;
        }

色々なリクエストを送ってみると、こんな結果になる

リクエスURI 前方一致の結果 結果
/hoge/images/a.jpg ^~ /hoge/images/ 前方一致の結果で確定し /var/www/html/hoge/images/a.jpg が返される
/hoge/images/b.png ^~ /hoge/images/ 前方一致の結果で確定し/var/www/html/hoge/images/b.png が返される
/hoge/a.jpg /hoge/ 前方一致の結果は保留され、正規表現にマッチするので /data/hoge/a.jpg が返される
/hoge/b.png /hoge/ 前方一致の結果は保留されるが、マッチする正規表現がないので前方一致の結果が使われて403エラーとなる
/fuga/images/a.jpg /fuga/images/ 前方一致の結果は保留され、正規表現にマッチするので /data/fuga/images/a.jpg が返される
/fuga/images/b.png /fuga/images/ 前方一致の結果は保留されるが、マッチする正規表現がないので /var/www/html/fuga/images/b.png が返される
/fuga/a.jpg ^~ /fuga/ 前方一致の結果で確定し、403エラー
/fuga/b.png ^~ /fuga/ 前方一致の結果で確定し、403エラー

誤解していたのはlocationのマッチング処理順が

  1. =の完全一致
  2. ^~ありの前方一致
  3. 正規表現
  4. ^~なしの前方一致

であると思い込んでいたこと。実際は2の段階で^~のあるものないもの両方がチェックされるので ^~ にマッチするlocationが存在しても、^~なしのlocationが最長一致で選ばれて正規表現マッチに移行する可能性がある。

自分は ^~ をあまり使わないので誤解していても実用上問題なかったのだが、これは非常によろしくない。公式のドキュメントはちゃんと読みましょう。

で、nginxのドキュメントを読むと・・・

nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered.

最初に前方一致マッチで一番長くマッチするやつを選んで記憶する

Then regular expressions are checked,

次に正規表現をチェック

If the longest matching prefix location has the “^~” modifier then regular expressions are not checked.

最長マッチしたlocationが ^~ を持っていたら正規表現はチェックしない

using the “=” modifier it is possible to define an exact match of URI and location. If an exact match is found, the search terminates.

=を使うとlocationとURIの完全一致を定義できて、完全一致した場合は検索を終了する