Laravelの環境構築してて、nginxで頭を抱えてしまっている人向け


🚨 記事内容精査中です。

# ■ 実際にあった未経験エンジニアの物語

#### 1章 Webサーバーソフトウェアを知る

あなたはプログラミングの世界に新たに足を踏み入れ、Webアプリケーションを開発する際にはWebサーバーソフトウェアが必要だということを学びました。このWebサーバーソフトウェアは、インターネット上でユーザーとアプリケーションの間の通信を担う重要な役割を果たすと理解しました。[^webサーバーソフトウェア]

#### 2章 Laravelプロジェクトの始動

あなたはLaravelというPHPフレームワークを使い始め、自身のプロジェクトを立ち上げます。すると、ブラウザ上であなたのプロジェクトのページが表示されていることに気づきます。この時点で、あなたはまだ専用のWebサーバーソフトウェアを設定していないはずです。

#### 3章 誤解

「LaravelはWebサーバーソフトウェアがなくてもなんとか上手く動くのだろうか？」という疑問が浮かびましたが、なんかうまく動いているし、深くは考えませんでした。
（実はあなたの知らないうちにPHPのビルトインサーバー[^PHPビルトインサーバー]が背後で動作しており、ブラウザでの表示を可能にしていました。このとき、`php artisan serve`という魔法のコマンドがそのビルトインサーバーを起動させていることをあなたは知る由もなかった。）

:::note warn

**筆者メモ**
１年目の後輩にヒアリングしたところ、php artisan serveで起動しているのがサーバーだという感覚はなく、何かLaravelのプログラム群をランニング状態にするという感覚があると教えてもらいました。つまり、プログラム自体にON/OFF状態があるという感覚があるようです。確かに直観的にはそう考えることもありそうだなと感じました。
:::

#### 4章 忍び寄るnginx

ある日、あなたはチームの他のメンバーが環境構築について話しているのを耳にします。彼らはWebサーバーとしてnginxを使用することを検討していました。あなたは疑問に思います。「Laravelを使っているんだから、Webサーバーは必要ないのでは？」と。この矛盾があなたの脳を停止させました。

#### 5章 発見！ビルトインサーバー

あなたの調査と学習を通じて、ついにビルトインサーバーの存在とその役割を理解します。`php artisan serve`コマンドがビルトインサーバーを起動していたこと、そしてこれが開発用の簡易的なサーバーであることが明らかになります。

#### 6章 本番運用とnginxへの移行

更に深く掘り下げると、ビルトインサーバーが本番環境には適していないことがわかります。本番環境では、安定性、セキュリティ、スケーラビリティが求められ、これらを満たすためにはnginxのような本格的なWebサーバーが必要です。あなたはnginxを使えるようになることの重要性を理解し、その学習に取り組み始めます。

# ■ nginxの設定ファイルを理解する

この記事でのゴールは、

- [ ] dockerを利用して、nginxとphp-fpmを組み合わせたLaravelアプリケーションの実行環境を構築する

ことです。さっそく、説明を始めていきます。

## ▼ nginx.confファイル

通常nginxをインストールすると、`/etc/nginx/`ディレクトリに`nginx.conf`ファイルが配置されます。ここに設定を書き込んでいきます。

## ▼ 設定項目の構造

### ⦿ ディレクティブ

ディレクティブは、Nginxの設定ファイル内でサーバーの動作を指示する命令です。ディレクティブは名前とパラメーターで構成され、セミコロン(;)で終了します。例えば、`listen 80;` はNginxにポート80でリッスンするよう指示するディレクティブです。

### ⦿ コンテキスト

コンテキストは、ディレクティブをグループ化して、特定のセクションや条件下でのみ適用されるようにするためのものです。コンテキストは、`{` と `}` で囲まれたブロック内にディレクティブを記述することで定義されます。主なコンテキストには、`http`, `server`, `location` などがあります。

### ⦿ 主なコンテキストの解説

```nginx
# mainコンテキスト

# httpディレクティブ
http { # httpコンテキスト
    # serverディレクティブ
    server { # serverコンテキスト
        listen 80; # listenディレクティブ
        server_name example.com; # server_nameディレクティブ

        # locationディレクティブ
        location / { # locationコンテキスト
            root /var/www/example.com; # rootディレクティブ
            index index.html; # indexディレクティブ
        }

        # locationディレクティブ
        location /images/ { # locationコンテキスト
            root /var/www/example.com/images; # rootディレクティブ
        }
    }
}

```

<details><summary>設定ファイル👆の内容</summary>

:::note info

設定ファイル👆の内容

今は、詳しい内容を理解する必要はありません。設定ファイルの書き方の雰囲気だけつかめればOKですが、一応解説をつけておきます。この設定では、`example.com` へのHTTPリクエストをポート80で受け付け、ルートディレクトリとして `/var/www/example.com` を使用します。また、`/images/` へのリクエストに対しては `/var/www/example.com/images` ディレクトリからファイルを提供します。

:::

</details>

#### mainコンテキスト

`main` コンテキストは、Nginx設定ファイルの最上位レベルに位置し、特定のコンテキストブロックに包含されないディレクティブの集まりです。これらのディレクティブは、Nginxサーバー全体に適用され、`http`、`server`、`location` などの他のコンテキストで上書きされない限り、その設定が全般に影響します。`main`　コンテキストは最上位に位置しているため、わざわざ`{}`で囲む必要はありません。

#### httpコンテキスト

`http` コンテキストは、HTTPに特化した設定をグループ化するために使用されます。この中で指定されたディレクティブは、Nginxが扱うすべてのHTTP通信に適用されます。例えば、キャッシュの挙動や、ログファイルのパスなどを指定できます。

#### serverコンテキスト

`server` コンテキストは、特定のドメインやポートに対する設定を定義します。複数の `server` ブロックを定義することで、1つのNginxサーバーで複数のサイトをホストすることができます。`server_name` や `listen` ディレクティブを使用して、どのドメインやポートに対する設定かを指示します。

#### locationコンテキスト

`location` コンテキストは、URIの特定のパターンに基づいてリクエストを処理するための設定を定義します。例えば、特定のパスへのリクエストに対して特定のファイルを返したり、プロキシサーバーを経由して処理を行うなどの設定が可能です。

## ▼ デフォルトのnginx.confを見てみよう

```nginx
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}
```

<details><summary>📝 デフォルトのnginx.confの中身を確認する方法</summary>

nginxをインストールすれば、デフォルトのnginx.confを確認できます。
後の事を考えて、docker-composeでnginxコンテナを立ち上げて、確認しましょう。

```
laravel-nginx/
├── nginx
│   └── Dockerfile
└── docker-compose.yml
```

```yml: docker-compose.yml
networks:
  laravel-nginx:
    driver: bridge

services:
  nginx:
    build:
      context: ./nginx
      dockerfile: Dockerfile
    ports:
      - '8080:80'
    networks:
      - laravel-nginx
```

```dockerfile: Dockerfile
FROM nginx:1.25.3-alpine3.18

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
```

```bash: ターミナルで実行
docker compose up -d
docker compose exec nginx sh
cat /etc/nginx/nginx.conf
```
--------------
</details>

### ⦿ mainコンテキスト

- `user`
    - Nginxワーカープロセスが動作するユーザーを指定します
- `worker_processes`
    - Nginxが生成するワーカープロセスの数を指定します。autoに設定すると、Nginxは自動的に利用可能なCPUコアの数に基づいてワーカープロセスの数を決定します。これにより、パフォーマンスとスケーラビリティが最適化されます
- `error_log`
    - エラーログの出力先とログレベルを指定します。ここで指定されたファイルには、エラーメッセージや警告などが記録されます。ログレベル（この例ではnotice）を指定することで、どのレベルのログメッセージを記録するかを制御できます。ログレベルには「debug < info < notice < warn < error < crit < alert < emerg」順に高くなり、設定したレベル以上のログが出力されます
- `pid`
    - このディレクティブはNginxのメインプロセスのPID（プロセスID）をファイルに記録する場所を指定します。Nginxを再起動または停止する際にこのPIDファイルが使用されます

### ⦿ eventsコンテキスト

- `worker_connections`
    - このディレクティブは、1つのワーカープロセスが同時に開くことのできる最大接続数を指定します
    - サーバーの同時接続数は `worker_processes×worker_connections` で決定されます。例えば、`worker_processes` が `4` に設定されている場合、全体での最大接続数は `4×1024=4096` 接続となります

### ⦿ httpコンテキスト

- `include`
    - このディレクティブは、他の設定ファイルを現在の設定ファイルに含めることを指示します。`/etc/nginx/mime.types`は、さまざまなファイルタイプに対するMIMEタイプの定義を含むファイルです。`include /etc/nginx/conf.d/*.conf;`は、`/etc/nginx/conf.d/`ディレクトリ内の全ての.confファイルを含めることを指示し、柔軟な設定管理を可能にします
- `default_type`
    - MIMEタイプが明示的に指定されていない場合に使用されるデフォルトのコンテンツタイプを設定します。application/octet-streamは、特定のタイプが分からないバイナリデータを指す一般的なタイプです
- `log_format`
    - ログに記録される情報のフォーマットを定義します。`main`はこのフォーマット名を意味し、それ以降部分で具体的なフォーマットを指定しています
- `access_log`
    - アクセスログの出力先と使用するログフォーマットを指定します。この場合、ログファイルは`/var/log/nginx/access.log`に保存され、log_formatで定義された`main`フォーマットが使用されます
- `sendfile`
    - 静的コンテンツの配信時に、カーネルスペースからユーザースペースへのファイルコピーを回避し、効率を向上させるために`sendfile`システムコールを使用するかどうかを制御します。`on`に設定することでパフォーマンスが向上します
- `keepalive_timeout`
    - キープアライブ接続が閉じられるまでのタイムアウト時間（秒）を設定します。この値を適切に設定することで、クライアントとサーバー間の接続の再利用を促進し、通信の効率を向上させることができます
- `gzip` (コメントアウトされています)
    - HTTPレスポンスの圧縮を有効にするかどうかを制御します。コメントアウトされているため、この設定は無効です。有効化すると、ページのロード時間を短縮し、帯域幅の使用量を削減することができます

<details><summary>📝 log_formatについて深掘り</summary>

##### 構文

```nginx
log_format name 'string' ...;
```

- name
    - ログフォーマットの名前です。この名前は、access_logディレクティブで参照され、どのフォーマットを使用するかを指定する際に用います
- 'string' ...:
    - ログに記録される具体的なフォーマットを指定する文字列です。文字列内では、固定テキストと変数を組み合わせることができます。変数は$に続いて変数名が来る形式で、リクエストやレスポンスに関連する様々な値をログに含めることができます

##### 変数の使用

```nginx
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent" "$http_x_forwarded_for"';

```

この例では、mainという名前のログフォーマットが定義されており、以下の情報を含むログを生成します：

- `$remote_addr`: リクエストを送信したクライアントのIPアドレス
- `$remote_user`: Basic認証で認証されたユーザー名（存在する場合）
- `$time_local`: ローカルタイムゾーンに基づいたアクセス時間
- `$request`: リクエストライン（メソッド、パス、HTTPバージョン）
- `$status`: レスポンスのHTTPステータスコード
- `$body_bytes_sent`: クライアントに送信されたレスポンスのバイト数
- `$http_referer`: リファラーURL
- `$http_user_agent`: ユーザーエージェント
- `$http_x_forwarded_for`: プロキシ経由のリクエストの場合、オリジナルのクライアントIPアドレスを含むことがあります

Nginxはリクエスト、レスポンス、サーバー環境に関連する多くの変数を提供しています。これらの変数を使用して、ログに必要な情報を柔軟に含めることができます。詳細なリストや変数の説明については、Nginxの公式ドキュメントを参照してください。


</details>

# ■ FastCGI: Webサーバーとアプリケーションの橋渡し

FastCGIの説明に入る前に、Webサーバーとアプリケーションサーバー間の通信の基本をおさらいしましょう。Webサーバーソフトウェア（例えばnginx）は、インターネットからのリクエストを受け取り、それに応答する役割を持っています。しかし、動的なコンテンツを扱う場合、このWebサーバー単体ではリクエストを完結させることができません。そのため、アプリケーションサーバー（例えばPHPの実行環境）が必要となります。この二つのサーバー間の橋渡しをするのが、FastCGIの役割です。

FastCGIは、Webサーバーとアプリケーションサーバー間での効率的な通信を実現するプロトコルです。PHPの場合、この通信の効率化のためにPHP-FPM（FastCGI Process Manager）を利用します。PHP-FPMは、PHPの実行環境を管理し、FastCGIのプロトコルを通じてWebサーバー（例えばnginx）と通信します。この仕組みにより、PHPスクリプトの実行を効率的に行うことができ、アプリケーションのパフォーマンスを向上させることが可能になります。

ビルトインサーバーを使用していた時、このWebサーバーとアプリケーションサーバー間の橋渡しは、ほとんど意識されることがありませんでした。php artisan serveを実行すると、Laravelの開発環境が簡単に立ち上がり、ブラウザでアプリケーションを確認することができます。この時、PHPのビルトインサーバーが背後で動作していることは見えにくく、Webサーバーとアプリケーションサーバー間のやり取りがブラックボックス化していました。

#### FastCGIとPHP-FPMの役割
- FastCGI
    - Webサーバーとアプリケーションサーバー間の通信プロトコル。効率的なプロセス管理と持続的な接続を提供し、アプリケーションの応答性を高めます
- PHP-FPM
    - PHPの実行環境を管理するための高度なプロセスマネージャー。FastCGIプロトコルを使用してWebサーバーと通信し、PHPスクリプトの実行を効率化します

## ▼ 流れ


- [ ] Laravelドキュメントにあるnginxのserverディレクティブの設定項目について、理解する
- [ ] PHP-FPM上でLaravelアプリケーションを動かせるようにLaravelアプリケーション用のDockerfileを作成する


## ▼ Laravelドキュメントに記載されている設定項目について

Laravelのドキュメントには、nginxをWebサーバーとして利用する場合の、serverディレクティブの基本構成が紹介されています。まずは、これを理解し、必要に応じてカスタマイズしていくとよいでしょう。

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

```nginx
server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    root /srv/example.com/public;
 
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
 
    index index.php;
 
    charset utf-8;
 
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
 
    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }
 
    error_page 404 /index.php;
 
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }
 
    location ~ /\.(?!well-known).* {
        deny all;
    }
}
```

### ⦿ リスニングポート

- `listen 80;`
    - サーバーが**IPv4アドレス**でのリクエストをポート80でリッスンするように設定しています
- `listen [::]:80;`
    - サーバーが**IPv6アドレス**でのリクエストをポート80でリッスンするように設定しています

<details><summary>🤔 設定値の書き方がよくわからない</summary>

- IPv4の場合:
    - 全てのIPv4アドレスを受け入れる:
        - `listen 80;` と記述すると、サーバーはポート80で全てのIPv4アドレスからのリクエストを受け入れます
    - 特定のIPv4アドレスに制限する:
        - `listen 192.168.1.1:80;` のように記述すると、サーバーはポート80で指定した 192.168.1.1 というIPv4アドレスからのリクエストのみを受け入れます

- IPv6の場合:
    - 全てのIPv6アドレスを受け入れる:
        - `listen [::]:80;` と記述すると、サーバーはポート80で全てのIPv6アドレスからのリクエストを受け入れます。[::] はIPv6アドレスの全範囲を指します
    - 特定のIPv6アドレスに制限する:
        - `listen [2001:db8::1]:80;` のように記述すると、サーバーはポート80で指定した 2001:db8::1 というIPv6アドレスからのリクエストのみを受け入れます

このように、IPv4とIPv6の設定では、全てのアドレスからのアクセスを受け入れるか、特定のアドレスに制限するかを明示的に設定できます。IPv4ではポート番号のみを指定することで全アドレスをカバーし、IPv6では [::] を使用します。特定のアドレスに制限する場合は、そのアドレスを明確に記述します。

</details>

### ⦿ サーバー名

- `server_name example.com;`
    - この設定は、`example.com`というドメイン名でアクセスされたリクエストを処理するようサーバーに指示します


### ⦿ ドキュメントルート

- `root /srv/example.com/public;`
    - サーバーがWebコンテンツを探す基本ディレクトリを指定します

### ⦿ インデックスファイルの指定

- `index index.php;`
    - ディレクトリにアクセスされた際にデフォルトで提供するファイルを指定します。ここでは index.php がデフォルトのインデックスファイルとして設定されています

### ⦿ セキュリティ関連ヘッダー

- `add_header X-Frame-Options "SAMEORIGIN";`
    - クリックジャッキング攻撃を防ぐために、ページが同じオリジンのフレーム内でのみ表示されるように設定します
- `add_header X-Content-Type-Options "nosniff";`
    - MIMEタイプのスニッフィングを防ぎます

<details><summary> 🤔 そもそもヘッダーを追加する意味とは？</summary>

HTTPレスポンスのヘッダーにさまざまな項目を追加することで、ブラウザや他のクライアントに特定の動作を指示することができます。これらのヘッダーは、Webページのセキュリティを向上させたり、特定の動作を制御したりするために使用されます。

</details>

<details><summary>💡セキュリティヘッダーについてもっと詳しく</summary>

- X-Frame-Options:
    - X-Frame-Options ヘッダーは、あるページが \<iframe\> や \<frame\>、または \<object\> タグ内で表示されることを許可するかどうかを制御します
    - 例えば X-Frame-Options: SAMEORIGIN と設定すると、そのページは同じオリジンのドキュメント内でのみ表示でき、クリックジャッキング攻撃[^クリックジャッキング攻撃]を防ぐのに役立ちます
- X-Content-Type-Options:
    - X-Content-Type-Options ヘッダーは、特に nosniff オプションを使用して、ブラウザがMIMEタイプを推測（スニッフィング）するのを防ぎます
    - X-Content-Type-Options: nosniff と設定すると、ブラウザはサーバーから提供されたMIMEタイプの指示に従ってコンテンツを処理します。これにより、特定の種類の攻撃（例えば、悪意のあるスクリプトが正当なファイルタイプとして偽装されること）を防ぐのに役立ちます

memo: https://techblog.gmo-ap.jp/2022/12/09/mime_sniffing/


</details>

### ⦿ ロケーション


Nginx設定ファイルにおける`location`ブロックは、特定のURLパターンに基づいてリクエストを処理するための方法やルールを定義するものです。`location`ブロックを使用することで、異なるURLパターンに対して異なる設定や処理を適用できます。基本的な形式は以下の通りです:

```nginx
location [パターン] {
    # パターンにマッチした場合の設定
}
```
#### パターン

`location`ブロックのパターンには、シンプルな文字列マッチングから正規表現を使用した複雑なパターンマッチングまで、さまざまな形式が使用できます。

- 通常の文字列マッチング 👉 例: `location /images/`
    - このタイプはリクエストされたURIが指定された文字列で始まるかどうかをチェックします。この例では、URIが`/images/`で始まる全てのリクエストに適用されます。マッチングは最も単純で、正確な文字列の一致を基にしています
- 大文字小文字を区別する正規表現マッチング 👉 `location ~ \.php$`
    - `~`は大文字小文字を区別する正規表現マッチングを意味します。この例では、URIが.phpで終わる全てのリクエストに適用されます（例えばfile.php）。正規表現はより複雑なパターンマッチングを可能にします
- 大文字小文字を区別しない正規表現マッチング 👉 `location ~* \.jpg$`
    - `~*`は大文字小文字を区別しない正規表現マッチングを意味します。この例では、`.jpg`（大文字小文字を問わず）で終わる全てのリクエストに適用されます
- 最長一致優先 👉 `location ^~ /images/`
    - `^~`は、このロケーションブロックがマッチした場合、他の正規表現マッチングを無視して、このブロックを使用することを意味します
- 正確なマッチング 👉 `location = /favicon.ico`
    - `=`は完全一致マッチングを意味します。この例では、リクエストURIが正確に`/favicon.ico`と一致する場合にのみ適用されます。一致した場合は、このブロックの設定項目が最優先に適用されます

#### パターンマッチングの優先順位

1. 🥇 正確なマッチング:
location = /path のような形式のブロックは、完全一致する場合に最優先されます。
2. 🥈 正規表現マッチング:
location ~ /path や location ~* /path のような正規表現を使うブロックは、非正規表現のブロックより優先されます。ただし、複数の正規表現ブロックがマッチする場合、設定ファイル内で最初に見つかったものが使われます。
3. 🥉 最長一致優先:
location ^~ /path のような形式のブロックは、URIとの最長一致を試みます。これは非正規表現ブロックの中で特に優先されます。
4. 💩 一般的なマッチング:
location /path のような形式のブロックは、上記のいずれにも該当しない場合に適用されます。これらは設定ファイル内で上から順にマッチングが試みられます。

#### 各locationディレクティブの内容

各locationディレクティブの内容をマッチの優先度順に説明していきます。

```nginx
location = /favicon.ico { access_log off; log_not_found off; }
```
Laravelでは、ファビコンに設定したい画像を、publicディレクトリ直下に`favicon.ico`として格納しています。その画像を取得するための設定です。

```nginx
location = /robots.txt  { access_log off; log_not_found off; }
```

`robots.txt`によって、クロール不要なコンテンツを制御し、クロールさせたいページにだけ効率良くクローラーを誘導することができます。その`robots.txt`を取得するための設定です。

```nginx
location ~ \.php$ {
    fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    include fastcgi_params;
}
```
リクエストされたURLが `.php` で終わる（つまり、PHPファイルに対するリクエスト）場合にマッチするロケーションブロックを定義しています。

- `fastcgi_pass`
    - FastCGIサーバーの待ち受けアドレスを指定します
- `fastcgi_param`
    - この行は、FastCGIプロセスに渡される`SCRIPT_FILENAME`パラメータの値を設定します。`$realpath_root` は、リクエストされたファイルのルートディレクトリの絶対パスを示し、`$fastcgi_script_name` はリクエストされたスクリプトのパスです。これにより、FastCGIプロセスは実際に実行すべきPHPファイルを正確に知ることができます
- `include fastcgi_params;`
    - この行は、追加のFastCGIパラメータを含むファイルを指定します。fastcgi_params はNginxによって提供される標準のパラメータファイルであり、FastCGIプロセスに渡すべき一般的なパラメータが定義されています。これにより、スクリプト名、クエリストリング、リクエストメソッドなど、PHPスクリプトの実行に必要な情報がFastCGIプロセスに渡されます

```nginx
location ~ /\.(?!well-known).* {
    deny all;
}
```
`.well-known` ディレクトリ[^well-known]を除く、すべての隠しファイルおよび隠しディレクトリにアクセスを禁止するルールです。ここでの隠しファイルとは、名前がピリオド（.）で始まるファイルやディレクトリを指します。


```nginx
location / {
    try_files $uri $uri/ /index.php?$query_string;
}
```

上記のどの`location`ディレクティブにもマッチしなかった場合に、ここにたどり着きます。`try_files`ディレクティブは、指定されたファイルまたはディレクトリが存在するかどうかを順に検証し、最初に見つかったリソースに基づいてリクエストを処理します。最後までリソースが見つからない場合は、最後の引数の処理をします。本例では次のように動作します：

1. `$uri`
Nginxはまず、リクエストされたURIが実際のファイルとして存在するかを確認します。例えば、/example というリクエストがあった場合、対応する /example という名前のファイルがドキュメントルートに存在するかをチェックします。
2. `$uri/`
次に、リクエストされたURIがディレクトリとして存在するかを確認します。これは、リクエストがディレクトリに対して行われ、そのディレクトリが存在する場合に、そのディレクトリのインデックスファイル（例：index.html）を自動的に提供するために使用されます
3. `/index.php?$query_string`
上記のどちらも見つからない場合、リクエストは /index.php にフォワードされ、元のリクエストのクエリ文字列が付加されます。これにより、フロントコントローラーパターンを使用するアプリケーションで、すべてのリクエストを一つのPHPファイル（この場合はindex.php）に集約し、そのファイル内でリクエストのルーティングや処理を行うことが可能になります

# ■ nginx×Laravelのローカル開発環境構築

まずは、php-fpmで動作するLaravelアプリケーションを作成してます。
[Docker Installation Using Sail](https://laravel.com/docs/10.x/installation#docker-installation-using-sail)に従って、`nginx-laravel`ディレクトリ配下に`application`というLaravelプロジェクトを作成します。

```bash
curl -s "https://laravel.build/application" | bash
```

そこに、下記のような構成になるように、`nginx`ディレクトリや`Dockerfile`、`php.ini`などを追加します。

```
laravel-nginx/
├── application
│   ├── ...Laravelのディレクトリ群
│   ├── Dockerfile
│   └── php.ini
├── nginx
│   ├── Dockerfile
│   └── nginx.conf
└── docker-compose.yml
```

それぞれファイルを次のようにします。


```dockerfile: application/Dockerfile
FROM php:8.3-fpm-alpine3.19

WORKDIR /var/www/application

COPY ./ ./

RUN curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer

RUN composer install

COPY php.ini /usr/local/etc/php/conf.d/zz0-app.ini

EXPOSE 9000

CMD ["php-fpm"]
```

```ini: application/php.ini
[PHP]
log_errors_max_len = 0
default_charset = 'UTF-8'
default_mimetype = ''

[opcache]
opcache.enable_cli=1
opcache.revalidate_freq = 0
opcache.validate_timestamps = 1

[Session]
session.cookie_secure = 1
session.cookie_httponly = 1

[MySQLi]
mysqli.allow_persistent = 0

[Date]
date.timezone = 'UTC'
```

```dockerfile: nginx/Dockerfile
FROM nginx:1.25.3-alpine3.18

COPY ./nginx.conf /etc/nginx/nginx.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
```

```conf: nginx/nginx.conf
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    # 💡default.confを読み込むとそちらのserverディレクティブがlocalhostにマッチしてしまうので、includeしない
    # include /etc/nginx/conf.d/*.conf;

    server {
        listen 80;
        listen [::]:80;
        server_name localhost;
        # 💡　サーバールートを変更
        root /var/www/application/public;

        add_header X-Frame-Options "SAMEORIGIN";
        add_header X-Content-Type-Options "nosniff";

        index index.php;

        charset utf-8;

        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }

        location = /favicon.ico { access_log off; log_not_found off; }
        location = /robots.txt  { access_log off; log_not_found off; }

        error_page 404 /index.php;

        location ~ \.php$ {
            # 💡 php-fpmのコンテナに向き先を変更
            fastcgi_pass application:9000;
            fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
            include fastcgi_params;
        }

        location ~ /\.(?!well-known).* {
            deny all;
        }
    }
}

```

```yml: docker-compose.yml
networks:
  nginx-laravel:
    driver: bridge

services:
  application:
    build:
      context: ./application
      dockerfile: Dockerfile
    ports:
      - '9000:9000'
    volumes:
      - ./application:/var/www/application
      - nginx-laravel-vendor:/var/www/application/vendor
    networks:
      - nginx-laravel

  nginx:
    build:
      context: ./nginx
      dockerfile: Dockerfile
    ports:
      - '80:80'
    depends_on:
      - application
    networks:
      - nginx-laravel

volumes:
  nginx-laravel-vendor:
    driver: local
```

```bash: setup
docker compose build
docker compose run application php artisan key:generate
docker compose up -d
```

[http://localhost](http://localhost)にアクセスすると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166480/94bee583-2443-9fee-e0cc-811e23b18201.png)


# ■ 参考

https://entreprogrammer.jp/laravel-nginx-conf/

https://qiita.com/michida/items/d5c0fc5096d80ce49229

[^webサーバーソフトウェア]: [【Webサーバーソフトウェアとは】Apache/Nginx/LiteSpeed](https://webst8.com/blog/web-server-software/)

[^PHPビルトインサーバー]: [PHPビルトインサーバー](https://www.php.net/manual/ja/features.commandline.webserver.php#features.commandline.webserver)

[^クリックジャッキング攻撃]: [安全なウェブサイトの作り方 - 1.9 クリックジャッキング](https://www.ipa.go.jp/security/vuln/websecurity/clickjacking.html)

[^well-known]: [.well-knownフォルダについて](https://support.neoworks.jp/ssl/guide/well-known/)