Nginxでトレイリングスラッシュとファイル拡張子を削除する秘伝わざ
はじめに
Nginxのファイル配信のみでWebサイトを構築するとき、トレイリングスラッシュとファイル名を表示させないようにする方法を紹介します。
トレイリングスラッシュとは
トレイリングスラッシュは、パスの末尾につくスラッシュ記号のことです。
http://example.com/about
http://example.com/about/
上がトレイリングスラッシュなしで、下がトレイリングスラッシュありです。
トレイリングスラッシュの有無には明確なルールがあります。ディレクトリの中にあるindex.html
やindex.php
を配信するとき、よくindex.html
やindex.php
が省略されますが、この省略された状態のパスにはトレイリングスラッシュがつくことになっています。
つまり、上記の例だと、about
というディレクトリの中のindex.html
を配信するときにトレイリングスラッシュがつきます。
一方で、パスの末尾がファイル名で終了していた場合はトレイリングスラッシュがつきません。たとえば上記の例だと、about
というファイルを配信する場合はトレイリングスラッシュがつきません。また、Nginxの設定でtry_files
を利用するとファイルの拡張子を省略した場合にもファイルを配信するようにできます。try_files
を設定すれば、たとえば、about.html
というファイルを配信するときにhttp://example.com/about
としてアクセスすることができます。この場合も末尾のパスがファイル名を意味するのでトレイリングスラッシュがつきません。
気になる点
上記のようにトレイリングスラッシュありなしにはルールがあるため、好みのほうを自分で選択するものではないのですが、個人的にはどうしてもこのトレイリングスラッシュが気になってしまいます。サーバサイドのアプリケーションフレームワークを使ってルーティングをすればトレイリングスラッシュなしにすることができますが、Nginxのファイル配信だけでも同様のことをしたいのです。
また、index.html
やabout.html
など、ファイル名を省略してアクセスした場合でも、下記の例のように明示的にファイル名をつけてアクセスすることができてしまうのも、なんとなくすっきりしないと感じてしまいます。
http://example.com/about.html
http://example.com/about/index.html
それから細かいことではありますが、ディレクトリの中のindex.html
を配信する際に、もしindex.html
が存在しない場合は、404 Not Found
ではなく 403 Forbidden
を返してしまうのも気になります。
- トレイリングスラッシュをなしにしたい
- ファイル拡張子を隠蔽したい
- ディレクトリ参照でファイルが見つからないとき404を返す
今回はこの3つを実現するためのNginxの設定を紹介します。
環境
- Nginx 1.10.2
実現方法
今回の個人的な解決法では、HTML(CSSやJSなど含む)のみの場合とPHPを使用する場合とで設定が変わります。
HTMLのみの場合
Nginxの設定ファイルを以下のようにしてください。http
コンテキストは省略しています。
server {
listen 80;
server_name example.com;
root /home/username/www;
index index.html;
try_files $uri.html $uri/index.html $uri =404;
location ~ \.html$ {
internal;
}
location ~ index$ {
internal;
}
error_page 404 /404.html;
error_page 500 502 503 504 /500.html;
location = /404.html {
root /home/username/error_page;
internal;
}
location = /500.html {
root /home/username/error_page;
internal;
}
}
example.com
にはドメインを、/home/username/www
にはファイルを配信するルートディレクトリ(ドキュメントルート)を設定します。また、404.html
や500.html
を配信するディレクトリは/home/username/www
以外のディレクトリ(上記の例では/home/username/error_page
)にしてください。そしてその中に、404.html
と500.html
を置き、それぞれのエラーページを作ります。
ポイントはtry_files
です。通常は、index
にindex.html
を設定することでディレクトリの中のindex.html
を探して配信してくれるのですが、これだけだとトレイリングスラッシュがついてしまいます。
try_files
に$uri/index.html
を設定することでトレイリングスラッシュなしでもアクセスできるようになります。また、$uri.html
を設定することでファイルの拡張子を省略した場合でも、.html
がつくファイルを探して配信してくれるようになります。最後の=404
は、このいずれにも該当しない場合は 404 Not Found
を返すという意味になります。これで、ディレクトリ参照のときにファイルが見つからなかったら403
ではなく404
になります。
上記の設定だとトレイリングスラッシュをなしにしたり、ファイル名を省略することが出来ますが、ファイル名をつけた場合にも同様に配信されてしまいます。それを解決するのが location ~ \.html$
ディレクティブです。
これは、パスの末尾が.html
で終わっていた場合に該当します。つまりhttp://example.com/about.html
のようにアクセスした場合に該当します。これを配信しないようにするためにinternal
を設定しています。internal
は、Nginxが内部的に該当ファイルを探して見つかった場合には配信しますが、直接そのファイルを参照するようなGETリクエストがあった場合には配信しません。たとえばhttp://example.com/about
のようにアクセスして、Nginxがabout.html
を参照する場合は配信されますが、http://example.com/about.html
のように直接アクセスした場合は配信されません。
また、location = /404.html
と location = /500.html
では、エラーページを返す際に、直接アクセスしたときにそのまま配信されないように設定しています。たとえば、http://example.com/500.html
でアクセスするとサーバエラーでもないのにサーバエラーのページが表示されてしまうので、これを制御します。http://example.com/404.html
の場合はある意味 Not Found
なので要らないんじゃないかと言う人もいますが、表示されるページが同じでもinternal
を設定しない場合は 200 OK
としてレスポンスを返してしまうので、つけるほうが好ましいです。
ちなみに404.html
や500.html
のファイルを別のディレクトリに設置しているのは、仮に同じディレクトリに配置してしまうと、http://example.com/404
やhttp://example.com/500
でもアクセスできるようになってしまい、それらにinternal
をつけることを考えると設定が増えてしまうため、敢えて別ディレクトリで管理するようにしています。また、このようにエラーページ専用のディレクトリを設けることで、複数のドメインでエラーページを使いまわしできるというメリットもあります。
location ~ index$
に関してですが、これはhttp://example.com/index
でアクセスしたパターンでの制御です。http://example.com/index.html
では\.html$
にマッチするので配信されませんが、http://example.com/index
の場合は配信されてしまうので追加しています。
以上で、トレイリングスラッシュなし、ファイル拡張子の隠蔽、403 Forbidden を返さない設定ができました。試しに以下のようなディレクトリ構成をして確認してみましょう。
/home/username/www
├── about
│ └── index.html
├── empty
├── index.html
└── profile.html
以下のパスでは、404 Not Found
となりファイルが配信されません。
http://example.com/index.html
http://example.com/index
http://example.com/profile.html
http://example.com/profile/
http://example.com/about/index.html
http://example.com/empty
http://example.com/empty/
http://example.com/404
http://example.com/404.html
http://example.com/500
http://example.com/500.html
以下のパスでは 200 OK
となりファイルが配信されます。
http://example.com
http://example.com/about
http://example.com/about/
http://example.com/profile
上記を見て、3つめの例ではトレイリングスラッシュがありますが、about
ディレクトリの中のindex.html
を配信する際には、トレイリングスラッシュありなし、どちらでもアクセスできるようになります。一方で、profile.html
を配信する際にはトレイリングスラッシュありではアクセスできません。
これに関してですが、トレイリングスラッシュの有無は、一般的には無視されがちなので、ユーザがトレイリングスラッシュをつけてアクセスして 404 Not Found
を返してしまうなど混乱の元になりますので、トレイリングスラッシュありでもアクセスできるようにはしておいたほうが良いというのが個人的見解です。ちなみにtry_files
を設定しない場合だと、http://example.com/about
でアクセスしたときにhttp://example.com/about/
にリダイレクトされてしまうので、トレイリングスラッシュなしでアクセスできません。
トレイリングスラッシュありでのアクセスを許容したくない場合は、about.html
のように設定してください。なお、about.html
とabout
ディレクトリを混在させると、トレイリングスラッシュをつけた場合とつけない場合で別々のファイルが参照されてしまうので注意してください。
PHPを使用する場合
以下のように設定してください。
server {
listen 80;
server_name example.com;
root /home/username/www;
index index.php;
try_files $uri.php $uri/index.php $uri =404;
location / {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
try_files $uri.php $uri/index.php $uri =404;
}
location ~ \.(css|js|txt)$ {
root /home/username/www;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
internal;
}
location ~ \.html$ {
internal;
}
location ~ index$ {
root /home/username/www;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
internal;
}
error_page 404 /404.html;
error_page 500 502 503 504 /500.html;
location = /404.html {
root /home/username/error_page;
internal;
}
location = /500.html {
root /home/username/error_page;
internal;
}
}
この設定ファイルには、2つの注意点があります。
-
.php
以外に配信したいファイルがある場合は、その拡張子を別途追加する必要がある -
.html
は配信できない
\.php
やindex$
、エラーページの設定などはHTMLの場合と同じです。fastcgi
等の設定はPHPを配信するために必要なだけなのでここでは説明は省略します。
fastcgi
に関する設定はserver
ディレクティブ直下に記述することができないため、location /
ディレクティブに記述しています。そのときのポイントとして、location
ディレクティブの中にもtry_files
を入れることです。他のlocation
ディレクティブにマッチしないときはすべて location /
にマッチしますが、ここでfastcgi
によってPHPが解釈されますが、try_files
がないと、try_files
によってマッチしたことがPHP側で解釈されないので、配信してはいけないものだと判断して、403 Forbidden
を返してしまいます。
location \.(css|js|txt)$
は.php
以外のファイルを配信するときの設定です。先ほど説明した通り、location /
以外の複数あるlocation
ディレクティブのうち、どれにもマッチしない場合はlocation /
にマッチします。そしてlocation /
にはfastcgi
の設定が書かれているため、すべてPHPとして解釈されます。CSSファイルやJavaScriptファイルを配信しようとしたときに、location /
にマッチしてしまうとPHPとして解釈されてしまうので 403 Forbidden
となってしまいます。
なので、css, js, txt 以外に配信したいファイルがある場合は、各自で追加してください。これに関してはスマートではないですが他に良い設定が思い浮かばなかったです。もしもっとスマートな設定法がありましたら教えていただけると嬉しいです。
この設定では、.php
と.html
を混在させて使用することが困難になっています。そのためエラーページ以外のHTMLは配信することが出来ません。404.html
と500.html
に関してはディレクトリが異なるのでエラーになったときに限り配信することができます。通常のHTMLファイルを配信する際は、拡張子を.html
から.php
とすることで配信することが出来るようになります。こちらもスマートな方法ではないですが、PHPファイルは、PHPのタグを使わなければ実質HTMLファイルのようなものなので、特に支障はないかと思います。
以上でPHPを使用する場合の設定ができました。確認例はHTMLの配信の場合と同様ですが、CSSやJavaScriptのファイルを作成して、それらのファイルが参照できることを確認してください。
注意点
ブラウザは、ステータスコードが 301 Moved Permanently だと、次回以降、キャッシュが削除されるまでリダイレクト前のURLにはアクセスしないという仕様があります。
Nginxの設定を間違えて、http://example.com/about
でアクセスした際にhttp://example.com/about/
に301リダイレクトしてしまったとします。その後、設定ファイルを正しいものに書き換えてNginxをリロードしたとしても、ブラウザは相変わらずhttp://example.com/about/
のみを参照してしまうことがあります。設定が正しかったとしてもトレイリングスラッシュがついてしまうことがあるので注意です。
これの解決策としては、この設定を試す際にはブラウザのシークレットモードを使用し、Nginxの設定を書き換えた場合はシークレットモードのウィンドウを一旦閉じて、もう一度シークレットモードのウィンドウを開きアクセスすると良いです。これによりブラウザは301リダイレクトを記憶しないので、設定が合っていた場合はトレイリングスラッシュなしでアクセスできるようになります。
ブラウザを起動してから、シークレットモードを使用せずに、検証用ドメインで、リダイレクトが発生するようなURLにアクセスしてしまった場合は、一度ブラウザを再起動することが必要かもしれません(未検証)
誤った設定
ネットで "nginx remove trailing slash" などで検索するとなぜかほとんどのサイトでは以下のように設定するように紹介されています。
server {
listen 80;
server_name example.com;
rewrite ^/(.*)/$ /$1 permanent;
}
要点となる部分だけを記述しています。
上記はrewrite
を使って、制御する方法です。条件が^/(.*)/$
なので、トレイリングスラッシュがある場合にマッチします。条件がマッチしたときにリダイレクトするパスが/$1
なので、トレイリングスラッシュがないパスにリダイレクトします。permanent
の有無はステータスコードが301
か302
かの違いです。
トレイリングスラッシュがあった場合はないものにリダイレクトする、という設定なので、一見正しい設定で、しかもたった1行でシンプルなのですが、実際にこれで試してみるとリダイレクトループが発生します。
先ほども述べましたが、Nginxは、たとえばabout
ディレクトリの中のindex.html
を参照する際に、http://example.com/about
のようにトレイリングスラッシュなしでアクセスすると、http://example.com/about/
に301リダイレクトするようになっています。これが無限ループを発生させる理由で、トレイリングスラッシュなしだとトレイリングスラッシュありにリダイレクトし、トレイリングスラッシュありだと上記の設定でトレイリングスラッシュなしにリダイレクトするので、トレイリングスラッシュありなしを永遠にリダイレクトしてしまうので、ブラウザがリダイレクトループを検出して、エラーとなってしまいます。
上記の設定だとうまくいかないのに、なぜか多くのサイトでこの方法が紹介されているので、もしかしたら、古いバージョンのNginxではこれでうまくいったのかもしれません(実際に、参考にしたサイトはどれも2〜3年前の記事ばかりでした)
また、try_files
を追加する例も多く見かけましたが、どのサイトも設定が不十分で、ディレクトリ内のindex.html
を配信する際にはやはりトレイリングスラッシュがついてしまうものが多かったです。
SEOに対する見解について
いろんなサイトを見ている中で、「トレイリングスラッシュありとなしで両方同じコンテンツが表示される場合はGoogleがコピーコンテンツと認識する」という情報を見かけました。
この記事の設定だと、about
ディレクトリにindex.html
があり、これを配信する際、http://example.com/about
とhttp://example.com/about/
どちらでもアクセスできるので、SEO的に問題なんじゃないかということが議論になります。
SEOに関しては詳しくないので、もしかしたらトレイリングスラッシュありなしで同じものが表示されるのはよくないことなのかもしれません。しかし、YouTube や GitHub, Twitter などの有名なサイトやRailsで作成したWebアプリケーションでは、トレイリングスラッシュありとなしで同じコンテンツが表示されます。
これは憶測ですが、トレイリングスラッシュありなしでコンテンツが重複していたとしても、それは検索エンジンとしても理解できるので特にペナルティにはならないと思われます。なので、個人的には特に問題がないと解釈しています。
もし、トレイリングスラッシュありとなしで両方同じものが表示されるのが気になる場合は、ディレクトリ内のindex.html
ではなくabout.html
のようなファイル配信にするか、トレイリングスラッシュをつけていた場合はinternal
を設定する、などの設定を追加して対応してください。
まとめ
Nginxのトレイリングスラッシュに関する情報はたくさんありましたが、どれを試しても自分の実現したいことに合致するサイトは見つらなかったので、結局いろいろ設定ファイルをいじりまくりながら自己解決しました。
Nginxは設定が簡便で非常に便利なので個人的にはとても気に入っています。ですが、今回みたいに凝ったことをするといろいろ試行錯誤しないといけません。この記事での実現方法はベストプラクティスではないかもしれませんが、同じことを実現しようとしている人の参考になれば幸いです。
Discussion