Laravel公式サイトに記載してあるNginxのconfを超絶丁寧に読み解いていく

# Laravel公式サイトに記載のNginxのconfを読み解く

元のconf内容はこちら。

https://laravel.com/docs/8.x/deployment#nginx


```conf:conf
# アクセスログとエラーログが標準出力/標準エラー出力されるようにシンボリックリンクを張る
# Docker を利用している場合、コンテナ内で動作しているプロセスのログは
# 標準出力/標準エラー出力に出力させるのがベストプラクティスだと言われている。
#
# Dockerのコンテナログは標準出力/標準エラー出力されたものをログとして記録する
# コンテナログに全て記録することで、ログの確認が楽。ログローテートの設定が楽になる。
access_log /dev/stdout main;
error_log /dev/stderr warn;

server {
    # listen リッスンするポート番号を設定。
    # 80番ポートをリッスンする。デフォルト80なのでこれはなくても問題ない。
    # docker-compose.ymlに記述するNginxコンテナのTargetのportと同一でないといけない。
    listen 80;

    # server_name リクエストヘッダ中の Host フィールドと一致した場合に設定が採用される。
    # もし、どのserver_nameともHostが一致しなかった場合、最初に定義されているserverが採用される。
    # もちろん自分でどのserverを採用するかも決めることができる。
    # その場合は、listen ディレクティブ にdefault_serverと記載すれば良い。
    # サーバ名をサーバのホスト名にしたい場合には、$hostname 変数が使える。IPでアクセスされた場合には効かないのでIPを書いておく必要がある。
    server_name example.com;

    # root ドキュメントルートを指定。
    # Laravelの仕様で、ドキュメントルートはプロジェクトのルートディレクトリではなく、publicというディレクトリにする。
    # 後で出てくる $document_root の値にもなる。
    root /srv/example.com/public;

    # add_header xxxx zzzz xxxxフィールドをレスポンスヘッダーに追加することができる。
    #
    # add_header X-Frame-Options "SAMEORIGIN";
    # クリックジャギング対策(悪意のあるサイトを透明にして被せておく)
    # 自身と生成元が同じフレーム内に限りページを表示する設定
    # 
    # SAMEORIGIN ページ元と同じオリジンの場合に表示できる。
    # オリジン = プロトコル・ドメイン・ポート
    add_header X-Frame-Options "SAMEORIGIN";

    # add_header X-XSS-Protection "1; mode=block";
    # XSSに対するフィルタ機能を強制的に有効にする
    # XSS攻撃を検出したときに、ページのレンダリングを停止する
    add_header X-XSS-Protection "1; mode=block";

    # add_header X-Content-Type-Options "nosniff";
    # Content-typeで指定されたMIMEタイプに必ず沿うようにできる。
    # これをしない場合、http://example.jp/sample.jsonというAPIのレスポンスが下記の場合に
    # 
    # HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8
    # { 
    #     "msg" : "<script>alert(1)></script>"
    # }
    #
    # このJSONをhttp://example.jp/sample.json/a.htmlのように、URLの後ろに「/a.html」を付け足して開くと、コンテンツが
    # JSONではなくHTMLとして解釈され、JavaScriptが動作してしまう
    add_header X-Content-Type-Options "nosniff";

    # URIが / で終わっているときに使われるファイル名。
    # なくてもいい。
    index index.php;

    # 文字コードの設定
    charset utf-8;

    # URIが / 場合に処理する
    location / {
	# try_files 左から順にファイルまたはディレクトリを探しにいく
	# try_filesの挙動については後述する。
        try_files $uri $uri/ /index.php?$query_string;
    }

    # faviconがない場合、アクセスする度に "GET /favicon.ico HTTP/1.1" 200 0 xxx とログが吐かれるのでそれをオフにする
    location = /favicon.ico { access_log off; log_not_found off; }

    # robots.txtは検索エンジンが読み込むファイル。
    # 上記と同じようにアクセスされた際のログを吐かないようにオフにしている。
    # ローカルでの開発であれば、公開することはないのでなくても良いがあっても困るものではないので書いておいていいだろう。
    location = /robots.txt  { access_log off; log_not_found off; }

    # 指定したエラーコードが発生したときに表示するページのURIを指定
    # コードには300〜599までの数値を記述できる。
    # リダイレクト先には外部のURIを指定することもできます。
    #    error_page 500 502 503 504 http://example.jp/sorry.html;
    error_page 404 /index.php;

    # 拡張子がphpであるファイルに対して処理する
    location ~ \.php$ {

        # UNIXドメインソケットで通信する
        # 同じホスト内でプロセス間通信を行うために利用(ファイルシステムを利用する。拡張子.sockが多い)
        # 違うホストの場合はソケット通信(TCPなどを使って行う)を使う形となる。
        # 要は同じホスト内でNginxプロセスとPHP-FPMプロセスの間で通信するために使用。
        # fastcgi_pass 127.0.0.1:9000 ←この設定は、同じホストでソケット通信を行うことになる。
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;

        # URIが"/"で終わっている(つまりディレクトリになっている)ときに使われるファイル名
        # 下で出てくる $fastcgi_script_name の値にもなる。
        fastcgi_index index.php;

        # fastcgi_param 環境変数に代入 
        # SCRIPT_FILENAMEに値(ここでは「$realpath_root$fastcgi_script_name」)を代入している。
        # $realpath_root にはドキュメントルートが入ってくる。
        # ($document_rootとかの方が個人的にわかりやすいので自分はそれにする)
        # example.com/ でアクセスすると /srv/example.com/public/index.php となる
        # $fastcgi_script_name にファイル名が入る。
        # example.com/hoge でアクセスすると /srv/example.com/public/hoge となる
        # 分かりにくいが$reqlpath_rootと$fastcgi_script_nameの値を結合した物が入る事になる。
        #
        # ↓add_headerというやつを設定して、curlで確認してあげれば、何が入っているかわかる
        # add_header my_document_root $document_root;
        # add_header fastcgi_script_name $fastcgi_script_name;
        # curl --head http://xxx もしくは curl -I http://xxxx
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;

		# ここでincludeするとSCRIPT_FILENAMEが$request_filenameで上書きされるはずなので、
		# fastcgi_paramより上に記述した方が良いはず。(/etc/nginx/fastcgi_paramsを確認する)
        include fastcgi_params;
    }

	# well-knownディレクトリを除く すべてのドットファイルへのアクセスを拒否する
	# well-knownディレクトリはSSL化を行うために利用するLet's Encryptが使用するため、
	# 開発環境においてなくても問題はないが、あっても困るものではない為、書いておいていいだろう。
    location ~ /\.(?!well-known).* {
        deny all;
    }
}

```

## try_filesについて

try_files 左から順にファイルまたはディレクトリを探しにいくと記載していたが、実はそれだけではない。
try_filesは複数かつ可変長の引数 file を取るが、最後の引数は uri もしくは code という別の扱いになっている。

[Module ngx_http_core_module](http://nginx.org/en/docs/http/ngx_http_core_module.html#try_files)より抜粋

```tex
Syntax:	try_files file ... uri;
        try_files file ... =code;
Default:	—
Context:	server, location
```

> Checks the existence of files in the specified order and uses the first found file for request processing; the processing is performed in the current context. 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. For example:
>
指定された順序でファイルの存在を確認し、最初に見つかったファイルをリクエスト処理に使用します。この処理は現在のコンテキストで実行されます。処理は現在のコンテキストで実行されます。ファイルへのパスは、ルートとエイリアスディレクティブに従って、fileパラメータから構築されます。ファイル名の最後にスラッシュを指定することで、ディレクトリの存在を確認することができます (例: "$uri/“)。
いずれのファイルも見つからなかった場合は、最後のパラメータで指定されたuriへの内部リダイレクトが行われます。例えば、以下のようになります。

```conf:conf
location /images/ {
    try_files $uri /images/default.gif;
}

location = /images/default.gif {
    expires 30s;
}
```

と言うことで最後のuriについては問答無用で内部リダイレクトを行う動きとなります。
confの記述の設定がされていた場合↓

```conf:conf
    index index.html index.htm index.php;

    # URIが / 場合に処理する
    location / {
        # try_files 左から順にファイルまたはディレクトリを探しにいく
        try_files $uri $uri/ /index.php?$query_string;
    }
```

### 例えば [example.com/hoge](http://example.com/hoge) でアクセスすると

[example.com/hoge](http://example.com/hoge) でアクセスすると$uri には "hoge" が入る。

ドキュメントルート配下にhogeというファイルが存在しなければ、 \$uri/ は "hoge/"となり、ドキュメントルート配下のhoge/というディレクトリを探しにいく。ディレクトリが存在しなければ、/index.php?\$query_string で内部リダイレクトを行い再評価が行われlocation ~ \.php$ と一致する為、php-fpmに処理が渡される。

### 例えば [example.com](http://example.com/hoge) でアクセスすると

[example.com](http://example.com/hoge) でアクセスすると\$uri は 空となる。空ファイルは存在しない為、\$uri/ は 空/ となり、これは403 Forbiddenのエラーとなる。(何故403となるのか。ここは正直よくわかってないので誰か教えてください。)
その為、\$uri/を消してあげれば、/index.php?$query_string で内部リダイレクトが行われる。

### 内部リダイレクト
普通のリダイレクトはレスポンスコードに301や302を指定し、Locationヘッダーフィールドにリダイレクト先にのURIを指定して返し、クライアントはそのURIに対して再びリダイレクトを行うが、内部リダイレクトは内部的にURIを書き換え、レスポンスコードなどは指定しない。クライアントからはリダイレクトしているようには見えない。

### codeについて
try_filesの説明にあったこちらのcodeについて。
> try_filesは複数かつ可変長の引数 file を取るが、最後の引数は uri もしくは code という別の扱いになっている
> try_files file ... =code;

try_filesの最後に任意のステータスコードをつけるとどのディレクティブにも一致しなかった場合、そのステータスコードで返すことができる。

下記のように =404; とつけると404が返される。
/index.php?$query_string; =404;

# 【余談】謎のphpファイルがダウンロードされる現象
下記のように / のlocationディレクティブと .php のlocationディレクティブが設定されていたとする。


```conf:conf
location / {
    try_files /index.php /sample.php;
}

location ~ \.php$ {
    fastcgi_index index.php;
    ...
}
```
ドキュメントルート配下にindex.phpファイルが存在する状態で、http://example.jp でアクセスを行うと、第一引数のfile(上記設定の/index.php)と一致することになる為、PHPファイルがダウンロードされる事になる。

これは、PHP-FPMディレクティブに処理が渡されていない為、MIMEタイプが octet-stream で返却される事によって起きている。Nginxコンテナの/etc/nginx/nginx.confnのhttpディレクティブを確認すると `default_type  application/octet-stream;` と記述してあることがわかる。octet-streamは任意のバイナリコードを意味し、ブラウザでは実行したりせず、単にダウンロードする挙動をとる。

> これは未知のバイナリ形式のファイルを表すものであり、ブラウザーはふつう実行したり、実行するべきか確認したりしません。

https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/MIME_types

↓以下をターミナルで実行すると、レスポンスヘッダーのContent-Typeがapplication/octet-streamとなっていることがわかる。
curl --head http://xxx もしくは curl -I http://xxxx

### PHP-FPMに渡すようにする

```conf:conf
location / {
    # 第一引数のfile(/index.php)を$uriに変更。
    try_files $uri /index.php?$args;
}

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

\$uriではPHP-FPM用の location ディレクティブ  location ~ \.php$ に飛ばしてPHP-FPMへ渡すようにすれば処理が行われる。

# ps.
わかり辛いところ、間違っているところ、文言おかしい、誤字脱字あれば教えてください。
後、ここは正直よくわかってないので誰か教えてください。と記載しているところもどなたか。。

# 参考記事
- [nginxのtry_filesの動作の確認](https://gist.github.com/kenjiskywalker/4596258)
- [nginxのtry_filesわかりにくすぎ問題 - tsyama記](https://tsyama.hatenablog.com/entry/try_files_is_difficult)
- [nginx連載5回目: nginxの設定、その3 - locationディレクティブ](https://heartbeats.jp/hbblog/2012/04/nginx05.html)
- [「UNIX ドメインソケット」と「ソケット」について比較する - Qiita](https://qiita.com/toshihirock/items/b643ed0edd30e6fd1f14)
- [nginx $document_root$fastcgi_script_name と $request_filename の違い - Qiita](https://qiita.com/kotarella1110/items/3b0bd84fdb55276f37d9)
- [nginxでLaravelを動かす時に困った - Qiita](https://qiita.com/mk-tool/items/0faff98d4108824b50ba)
- [nginx と PHP-FPM の仕組みをちゃんと理解しながら PHP の実行環境を構築する - Qiita](https://qiita.com/kotarella1110/items/634f6fafeb33ae0f51dc)
- [MIME TYPE application/octet-streamを返すときブラウザでは通信結果をダウンロードさせる - Qiita](https://qiita.com/chaspy/items/1b1419823519864c6cad)