無印吉澤

ソフトウェア開発、運用管理(俗にいう DevOps)、クラウドコンピューティングなどについて、吉澤が調べたり試したことを書いていくブログです。

nginx.conf の location の優先順位を正しく理解する

f:id:muziyoshiz:20171026000347p:plain

最近、Nginx の location 設定の優先順位を誤解していて軽くつまづきました。このへん時々よくわからなくなるので、自分のためにまとめておきます。

情報源

公式の説明は、マニュアルの Module ngx_http_core_module ページ内の "location" の項にあります。

nginx.org

location の修飾子(modifier)

location を定義する際に使える修飾子は、以下の5種類です。

修飾子 判定方法
= 完全一致
なし 前方一致
^~ 前方一致
~* 正規表現(case-insensitive)
~ 正規表現(case-sensitive)

前方一致が2種類ありますが、これは「^~ は省略可能」という意味ではありません

location の優先順位

location の優先順位を考える上でややこしい点は、書かれた順に評価される設定と、最長一致で評価される設定が混在していることです。特に、優先順位をややこしくしているのが ^~ の存在です。

Nginx は、あるリクエスト URI に対する configuration を決めるために、以下の手順で location を選択します。

  1. 完全一致の location を判定し、マッチしたらその location を選んで終了
  2. 前方一致の location を判定し、最も長い文字列がマッチしたものを記録する(最も長い文字列がマッチした location が ^~ 修飾子を使っていた場合に限り、その location を選んで終了)
  3. 正規表現の location を、設定ファイルに書かれた順で判定し、最初にマッチしたものが見つかった時点で終了
  4. 3 でマッチするものが見つからなかった場合は、2 で見つかった location を選んで終了

ポイントは、^~ にマッチしたらその場で判定が終わるわけではない、ということです。^~ なしの前方一致のほうが最も長い文字列(longest matching prefix)の場合は、手順3の正規表現の判定に進んでしまう、という点に注意が必要です。

もしも ^~ 修飾子を全く使わなければ、以下のように簡単に優先順位を説明できます。

  1. 完全一致
  2. ~* または ~ 修飾子で定義された正規表現のうち、設定ファイルに書かれた順で最も最初にマッチしたもの
  3. 修飾子なしで定義された前方一致のうち、最も長い文字列がマッチしたもの

結局 location はどういう順に書いたらいいか?

個人的には読みやすさを考えて、評価される順(自分が評価してほしいと期待する順)に書いておくのがよいと思います。また、^~ ありの前方一致は混乱を招くため、^/ から始まる正規表現として書いてしまったほうがよいのではないか、と思います。

  1. 完全一致の location を書く
  2. 優先したい前方一致の location を、^/ から始まる正規表現の location として書く(^~ は使わない)
  3. 正規表現の location を、優先したいものから順に書く
  4. 修飾子なしの前方一致の location を、長いものから順に書く

Nginx のマニュアルに書かれた location の例を微修正して、上のルールに従って書き直したものがこちらです。

# ルートへの完全一致
location = / {
    [ configuration A ]
}

# /images/ 以下の画像
location ~ ^/images/ {
    [ configuration D ]
}

# /images/ 以外に置かれた画像
location ~* \.(gif|jpg|jpeg)$ {
    [ configuration E ]
}

# /documents/ 以下のページ
location /documents/ {
    [ configuration C ]
}

# その他すべてのページ
location / {
    [ configuration B ]
}

まとめ

おそらくですが、適当な順序で location を書いてしまってもなんとなくそれなりに動くように、こういう仕様になっているのではないか、と思います。とはいえ、このへんを正確に理解しておかないと、他の人が書いた nginx.conf をいじるときにつまづきやすいので気をつけましょう。

参考ページ

location に関する説明を色々探してみて、このブログの記載が一番わかりやすくて参考になりました。

paulownia.hatenablog.com

おまけ:location の説明の日本語訳

以下、マニュアルの該当箇所の日本語訳です。古いバージョンの Nginx について書かれた記載や、特殊なケース(macOS など)について書かれたケースは割愛しました。

Sets configuration depending on a request URI.

location はリクエスト URI に応じて configuration を設定する。

The matching is performed against a normalized URI, after decoding the text encoded in the “%XX” form, resolving references to relative path components “.” and “..”, and possible compression of two or more adjacent slashes into a single slash.

マッチングは正規化された URI に対して実施される。正規化とは、"%XX" 形式でエンコードされたテキストのデコード、相対パスを表すコンポーネント "." および ".." への参照の解決、そして2つ以上の隣接したスラッシュの1個のスラッシュへの圧縮である。

A location can either be defined by a prefix string, or by a regular expression. Regular expressions are specified with the preceding “~*” modifier (for case-insensitive matching), or the “~” modifier (for case-sensitive matching). To find location matching a given request, 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, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.

location は prefix string または正規表現で定義できる。 正規表現は "~*" 修飾子(case-insensitive なマッチング)、または "~" 修飾子(case-sensitive なマッチング)で指定される。 与えられたマッチする location を探すために、nginx は最初に prex strings (prefix locations) を使って定義された location をチェックする。 そのチェックの間に、最も長い prefix とマッチした location が選ばれて記録される。 その次に正規表現が、設定ファイルの中で登場した位置の順にチェックされる。 正規表現の検索は、最初にマッチした段階で終了して、その location に書かれた configuration が使われる。 もし、そのリクエスト URL にマッチする正規表現が見つからなかった場合は、その前の段階で記録された prefix location の configuration が使われる。

location blocks can be nested, with some exceptions mentioned below.

location ブロックはネスト可能である。ただし、以下に示すいくつかの例外がある。

Regular expressions can contain captures (0.7.40) that can later be used in other directives.

正規表現は captures を含むことができる(0.7.40)。これは後の directives で使うことができる。

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

もし、longest matching prefix location が "^~" 修飾子を持っていたら、正規表現はチェックされない。

Also, using the “=” modifier it is possible to define an exact match of URI and location. If an exact match is found, the search terminates. For example, if a “/” request happens frequently, defining “location = /” will speed up the processing of these requests, as search terminates right after the first comparison. Such a location cannot obviously contain nested locations.

また、"=" 修飾子 を使うと、URI と location の完全一致を定義できる。 もし、完全一致が見つかったら、location の検索は終了する。例えば、もし "/" へのリクエストが頻繁に起こるなら、location = / を定義することでそれらのリクエストの処理を高速化できる。検索は最初の比較で完了するからである。そのような location が、ネストされた location を持つことは明らかにできない。

Let’s illustrate the above by an example:

上記のことを、例を使って説明しよう。

location = / {
    [ configuration A ]
}

location / {
    [ configuration B ]
}

location /documents/ {
    [ configuration C ]
}

location ^~ /images/ {
    [ configuration D ]
}

location ~* \.(gif|jpg|jpeg)$ {
    [ configuration E ]
}

The “/” request will match configuration A, the “/index.html” request will match configuration B, the “/documents/document.html” request will match configuration C, the “/images/1.gif” request will match configuration D, and the “/documents/1.jpg” request will match configuration E.

/ へのリクエストは configuration A にマッチする。"/index.html" へのリクエストは B にマッチし、"/documents/document.html" は C にマッチする。"images/1.gif" は D にマッチし、"/documents/1.jpg" は E にマッチする。

The “@” prefix defines a named location. Such a location is not used for a regular request processing, but instead used for request redirection. They cannot be nested, and cannot contain nested locations.

"@" 前置詞は名前付きの location を定義する。そのような location は正規表現の処理には使われないが、リクエストのリダイレクトのために使われる。"@" 付きの location はネストできないし、ネストされた location を含むこともできない。

If a location is defined by a prefix string that ends with the slash character, and requests are processed by one of proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass, or grpc_pass, then the special processing is performed. In response to a request with URI equal to this string, but without the trailing slash, a permanent redirect with the code 301 will be returned to the requested URI with the slash appended. If this is not desired, an exact match of the URI and location could be defined like this:

もし、location が、/ で終了する prefix string によって定義された場合、リクエストは proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass, または grpc_pass のうちの1つによって処理されて、その特別な処理が実行される。 この文字列に一致する URI を持つリクエスト、しかし末尾に / が付かない URI の場合は、その URI に対する恒久的なリダイレクトが、/ 付きの URI に対して返される。 もし、その動作が望ましくない場合、URI と location の完全一致を以下のように定義すればよい。

location /user/ {
    proxy_pass http://user.example.com;
}

location = /user {
    proxy_pass http://login.example.com;
}