tsyama記

プログラミングとそのほか

nginxのtry_filesわかりにくすぎ問題

はじめに

後輩にnginxの設定がうまくいかないと相談され、「しょうがないなァ」と乗り出したのはいいものの、よくわからず後輩と一緒に一時間ぐらいウンウン唸っていたので備忘録として。

前提

該当のサイトではWebサーバとしてnginxを使用し、PHPファイルについてはphp-fpmへ処理を渡します。
また、PHPフレームワークを使用しており、実体ファイルの存在しないURIが指定された場合はDocumentRoot直下のindex.phpに処理が行くようにしたいです。

location / {
    index index.php index.html index.htm;
    try_files $uri $uri/index.html $uri/index.htm $uri/index.php /index.php?$args;
}

location ~ \.php$ {
    fastcgi_index index.php;
    ...
}

確かこんな感じ。

この設定を反映させると、ドメインルート(https://example.com/)にアクセスされた際、PHPファイルがダウンロードされるようになってしまいました。

そもそもtry_filesとは

try_filesは引数を複数とります。左から順に実体ファイルが存在しているかどうかを探し、あればそのままそのファイルを参照してくれる、イカしたディレクティブです。

他にも、

try_files $uri $uri =404;

みたいにしてやると、ファイルが存在しない場合に404ステータスコードを返すこともできます。イカす。

try_filesの引数は最後のみ意味が違う

マニュアルも読まずに雰囲気で使っていると、「なるほど、try_filesは引数の左から順にファイルが存在するかを確認するディレクティブなんだな」という覚え方をしがちですが、厳密にはそうではありません。公式のマニュアルを引用します。

Syntax:  try_files file ... uri;
        try_files file ... =code;

先程も言ったように、try_filesは複数かつ可変長の引数fileを取りますが、最後の引数はuriもしくはcodeという別の扱いになっています。codeというのは先程説明した、ファイルが存在しない場合に指定のステータスコードを返すための処理です。今回はもう一つの

try_files file ... uri;

という用法について詳しく見てみます。

nginx.org

The path to a file is constructed from the file parameter according to the root and alias directives. It is possible to check directory’s existence by specifying a slash at the end of a name, e.g. “$uri/”. If none of the files were found, an internal redirect to the uri specified in the last parameter is made.

ファイルへのパスはfile、ルートおよびエイリアスディレクティブに従ってパラメーターから構築され ます 。名前の最後にスラッシュを指定することにより、ディレクトリの存在を確認することができます(例:「$uri/」)。ファイルが見つからなかった場合uri、最後のパラメーターで指定されたものへの内部リダイレクト が行われます。

Google翻訳

存在するファイルを探すのはfile引数のみ、uri引数についてはファイルを探したりせず、file引数に該当するファイルがひとつも存在しなかった場合に問答無用で内部リダイレクトされるわけです。

file引数はファイルへのアクセス、uri引数は内部リダイレクト

マッチ後の挙動もfileuriで異なります。

fileは実体ファイルのパスを指定するだけなのでphp-fpmに処理を渡すことができません。(やりようはあるのかもしれませんが…)

"前提"で取り上げた例では、$uri/index.phpに実体ファイルがあったのですが、php-fpmに渡されないまま実体ファイルにアクセスされ、PHPファイルがそのままダウンロードされるという現象が発生しました。

stackoverflow.com

uriはそれと違って内部リダイレクトが発生するため、locationディレクティブの再評価がされるようです。"前提"の例でいうと、

location / {
    index index.php index.html index.htm;
    try_files $uri $uri/index.html $uri/index.htm /index.php?$args;
}

location ~ \.php$ {
    fastcgi_index index.php;
    ...
}

のようにしてあげると、ドメインルートにアクセスされた際にtry_filesディレクティブのfile引数にマッチするものがなく、uri引数である/index.phpに内部リダイレクトして再評価が行われ、\.php$を指定したlocationディレクティブからphp-fpmに処理が渡される、というわけです。

おわりに

雰囲気でnginxをやっている勢はわりと躓きやすいんじゃないかと思っています。