nginx と PHP-FPM の仕組みをちゃんと理解しながら PHP の実行環境を構築する


## 前書き

PHP の実行環境を構築する場合、ググると大体 nginx と PHP-FPM を設定し PHP の実行環境を構築する記事が沢山出てきます。インフラの知識がない頃、コピペでとりあえず PHP の実行環境を構築できたが、「とりあえず動いたけどよく分からない」という状態でした。  
「PHP-FPM って何？」  
「Apache の場合インストールしただけで PHP の実行環境構築できるよね？」  
「nginx と PHP-FPM の通信はどうなってんの？」  
...etc  
自分と同じような方々がいるかもしれないので、その頃調べてまとめておいた内容を下記に記します。

## 今回構築していく OS の環境

```vim:/etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.1 LTS"
```

## PHP-FPM って何？

### Answer

[PHP: FastCGI Process Manager (FPM) - Manual ](http://php.net/manual/ja/install.fpm.php)

 > FPM ( FastCGI Process Manager ) は PHP の FastCGI 実装のひとつで、 主に高負荷のサイトで有用な追加機能を用意しています。

### そもそも CGI って何？

[Common Gateway Interface (CGI) - Wikipedia](https://ja.wikipedia.org/wiki/Common_Gateway_Interface)

> **Common Gateway Interface（コモン・ゲートウェイ・インタフェース、CGI）は、ウェブサーバ上でユーザプログラムを動作させるための仕組み。** 現存する多くのウェブサーバプログラムはCGIの機能を利用することができる。
> ウェブサーバプログラムの機能の主体は、あらかじめ用意された情報を利用者（クライアント）の要求に応じて送り返すことである。そのためサーバプログラム単体では情報をその場で動的に生成してクライアントに送信するような仕組みを作ることはできなかった。 そこでサーバプログラムから他のプログラムを呼び出し、その処理結果をクライアントに送信する方法が考案された。それを実現するためのサーバプログラムと外部プログラムとの連携法の取り決めが CGI である。

Web サーバー 上で PHP ( 動的コンテンツを生成する言語 ) を動作せるための仕組みです。

### じゃあ FastCGI は？

[FastCGI - Wikipedia](https://ja.wikipedia.org/wiki/FastCGI)

> FastCGIとは、Webサーバ上でユーザプログラムを動作させるためのインタフェース仕様の一つである。 **CGIの問題を解決するために** Open Market社によって1990年代中頃に開発された[1]もので、プロトコルは公開されている。

**CGI は、ユーザーから要求がある度に、プロセスの生成と破棄を行います。** 大量の要求があればその分だけプロセスの生成と破棄が実施され、この事がパフォーマンスの悪化に繋がっていきます。  
**FastCGI は、初回リクエスト時に起動したプロセスをメモリ上へ保持を行い、次回リクエストに対してはそのメモリに保持したプロセスの実行を行います。** CGI の問題を解決し、プログラム動作速度の向上およびサーバ負荷の低下が可能です。

## Apache の場合インストールしただけで PHP の実行環境構築できるよね？

### Answer

PHP には、モジュール版とCGI版という二種類があります。  
Apache ではモジュールとして PHP スクリプトを起動することができます。これにより、Webサーバーの起動時に読み込まれサーバーの処理の一部として実行されます。 PHP をインストールすると自動的に PHP モジュールもインストールされ、 Apache はデフォルトでその PHP モジュールを読み込みます。
Nginx では上記で説明した通り、 FastCGI を通してPHPを実行することができます。

### モジュール版？

[PHPのCGI版とモジュール版の違い | ServerKurabe](http://serverkurabe.com/php-cgi-module/)

> **モジュール版はCGI版の逆で、WebサーバーのプロセスのなかでPHPを実行してしまう方法です。** 現在多くのレンタルサーバーはWebサーバーにApache（アパッチ）を採用しているため、モジュール版というと、通常はApacheのモジュール版を意味します。
> ただPHPがWebサーバーを動かすユーザー（root権限など）で動作するため、ユーザーが複数いる共用サーバーではセキュリティ面に不安があります。そこでモジュール版には「セーフモード」という設定があり、ユーザー間のファイル干渉を防止できるようなっています。
> またモジュール版のメリットとして、WebサーバーのプロセスでPHPが実行されるため、CGIに比べて動作速度が高速になるという点があります。

### CGI版？

> **CGI版は実行ファイル形式とも呼ばれ、Webサーバーとは別のプロセスで実行されます。** 
このメリットとしては、まずセキュリティ面が挙げられます。CGI（版のPHP）を動かす各ユーザーは、Webサーバー本体を動かすユーザーとは異なります（切り離されています）。そのため誤って他ユーザーに干渉してしまうといった危険がありません。
> 一方デメリットとしては、Webサーバーとは別個のプロセスとして動かすぶん、実行するたびにメモリのロードが必要となり、動作速度がモジュール版に比べて遅くなります。

### Apache がデフォルトでモジュールを読み込んでいるのか確認してみる

#### インストール

```console
$ sudo add-apt-repository ppa:ondrej/php5-5.6
$ sudo apt-get update
$ sudo apt-get install -y php5 apache2
```

#### インストールされたかの確認

```console:php
$ php -v
PHP 5.6.17-3+deb.sury.org~trusty+1 (cli) 
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2015 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2015, by Zend Technologies
```

```console:apache2
$ apache2 -v
Server version: Apache/2.4.7 (Ubuntu)
Server built:   Jan 14 2016 17:45:23
```

#### Apache2の有効なモジュールの確認

Apache2 の現在有効なモジュールを置くディレクトリ は `/etc/apache2/mods-enabled/` です。

```apache:/etc/apache2/mods-enabled/php5.load
LoadModule php5_module /usr/lib/apache2/modules/libphp5.so
```

`php5_module` が読み込まれているかは、下記のコマンドでも確認できます。

```console
$ apache2ctl -M
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 10.0.2.15. Set the 'ServerName' directive globally to suppress this message
Loaded Modules:
 core_module (static)
 so_module (static)
 watchdog_module (static)
 http_module (static)
 log_config_module (static)
 logio_module (static)
 version_module (static)
 unixd_module (static)
 access_compat_module (shared)
 alias_module (shared)
 auth_basic_module (shared)
 authn_core_module (shared)
 authn_file_module (shared)
 authz_core_module (shared)
 authz_host_module (shared)
 authz_user_module (shared)
 autoindex_module (shared)
 deflate_module (shared)
 dir_module (shared)
 env_module (shared)
 filter_module (shared)
 mime_module (shared)
 mpm_prefork_module (shared)
 negotiation_module (shared)
 php5_module (shared)
 setenvif_module (shared)
 status_module (shared)
```

### Apache では FastCGI を通してPHPを実行することはできんの？

Apache でも可能です。興味がある方は下記の記事をご覧下さい。

[apache の FastCGI(mod_fastcgi) + PHP-FPM で phpを動かしてみる(CentOS,ScientificLinux編) | レンタルサーバー・自宅サーバー設定・構築のヒント](http://server-setting.info/centos/php_cgi_module_exchange.html)

## nginx と PHP-FPM の通信はどうなってんの？

### Answer

TCP か UNIX ドメインソケットのどちらかで通信するのがメジャーです。

### TCP と UNIX ドメインソケットって何？違いは？

下記の記事をご覧下さい。
非常にわかりやすく解説されており理解しやすいかと思います。

[調べなきゃ寝れない！と調べたら余計に寝れなくなったソケットの話 - Qiita](http://qiita.com/kuni-nakaji/items/d11219e4ad7c74ece748)

> UNIXドメインソケットは、TCPソケット(INETドメインソケット)よりも遥かにスループットが優れてるらしい。

## PHP の実行環境を構築してみる

### 環境

```vim:/etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.1 LTS"
```

### 準備

#### インストール

```
$ sudo add-apt-repository ppa:ondrej/php5-5.6
$ sudo apt-get update
$ sudo apt-get install -y php5 php5-fpm nginx
```

#### インストールされたかの確認

```console:php5
$ php -v
PHP 5.6.17-3+deb.sury.org~trusty+1 (cli) 
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2015 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2015, by Zend Technologies
```

```console:php5-fpm
$ php5-fpm -v
PHP 5.6.17-3+deb.sury.org~trusty+1 (fpm-fcgi)
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2015 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copy
```

```console:nginx
$ nginx -v
nginx version: nginx/1.4.6 (Ubuntu)
```

### 基本的な設定

```php:/etc/php5/fpm/pool.d/www.conf
; The address on which to accept FastCGI requests.
; Valid syntaxes are:
;   'ip.add.re.ss:port'    - to listen on a TCP socket to a specific address on
;                            a specific port;
;   'port'                 - to listen on a TCP socket to all addresses on a
;                            specific port;
;   '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
; listen = /var/run/php5-fpm.sock
; nginxのsites-available/defaultのfastcgi_passと同じpassにする
;listen = 127.0.0.1:9000
;listen = /var/run/php5-fpm.sock
```

```nginx:/etc/nginx/nginx.conf
# デフォルトサーバの設定
server {
    # バーチャルサーバが使用するアドレス、ポートを指定
    listen 80 default_server;

    # サーバの公開ディレクトリを指定
    # $document_root の値になる
    root /var/www;
    # インデックスページを指定
    index index.php index.html index.htm;

    # バーチャルサーバで使用するホスト名を指定
    server_name localhost;

    # URIごとにどのファイルを配信するか設定
    location / {
        # 指定したパスにファイルが存在するかどうか
        if (-f $request_filename) {
            # キャッシュの有効期限を設定
            expires 30d;
            break;
        }
    }

    location ~ [^/]\.php(/|$) {
        # PATH_INFO 部の分割に使用する正規表現を指定
        # 一つ目 ( .+\.php ) は $fastcgi_script_name の値になり、二つ目 ( /.+ ) は $fastcgi_path_info の値になる
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        if (!-f $document_root$fastcgi_script_name) {
            return 404;
        }
        # FastCGI サーバへリクエストをプロキシする
        #fastcgi_pass 127.0.0.1:9000;
        #fastcgi_pass unix:/var/run/php5-fpm.sock;
        # スラッシュで終わる URI の後に追加されるファイル名を設定
        # $fastcgi_script_name の値になる
        fastcgi_index index.php;
        # 設定ファイルを読み込む
        include fastcgi_params;
        # FastCGI サーバに渡されるべきパラメータを設定
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
    }
}
```

さらに理解を深めたい方は下記をご覧下さい。

* [nginx $document_root$fastcgi_script_name と $request_filename の違い - Qiita](http://qiita.com/kotarella1110/items/3b0bd84fdb55276f37d9)
* [nginx fastcgi_params を include する箇所、割と皆間違ってるよね？ - Qiita](http://qiita.com/kotarella1110/items/f1ad0bb40b84567cea66)

### 実行ユーザの設定

```nginx:/etc/nginx/nginx.conf
user www-data;
```

```php:/etc/php5/fpm/pool.d/www.conf
; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
user = www-data
group = www-data
```

### nginx と PHP-FPM の通信の設定

#### TCP の場合

##### 設定

```diff:/etc/php5/fpm/pool.d/www.conf
- ;listen = 127.0.0.1:9000
+ listen = 127.0.0.1:9000
  ;listen = /var/run/php5-fpm.sock
```

```diff:/etc/nginx/nginx.conf
- #fastcgi_pass 127.0.0.1:9000;
+ fastcgi_pass 127.0.0.1:9000;
  #fastcgi_pass unix:/var/run/php5-fpm.sock;
```

##### 再起動

```console
$ sudo service php5-fpm restart
$ sudo service nginx restart
```

##### TCP で通信できているかの確認

```console
$ netstat -a --tcp
tcp        0      0 localhost:9000          *:*                     LISTEN
```

#### UNIX ドメインソケットの場合

```diff:/etc/php5/fpm/pool.d/www.conf
  ;listen = 127.0.0.1:9000
- ;listen = /var/run/php5-fpm.sock
+ listen = /var/run/php5-fpm.sock
+ listen.owner = www-data
+ listen.group = www-data
```

```diff:/etc/nginx/nginx.conf
  #fastcgi_pass 127.0.0.1:9000;
- #fastcgi_pass unix:/var/run/php5-fpm.sock;
+ fastcgi_pass unix:/var/run/php5-fpm.sock;
```

##### 再起動

```console
$ sudo service php5-fpm restart
$ sudo service nginx restart
```

PHP-FPM を再起動したタイミングで `/var/run/php5-fpm.sock` が作成される。

##### UNIX ドメインソケットで通信できているかの確認

```console
$ netstat -a --unix
unix  2      [ ACC ]     STREAM     LISTENING     20655    /var/run/php5-fpm.sock
```

### 公開用ディレクトリの作成

```console
$ sudo mkdir /var/www/
$ sudo chown www-data:www-data /var/www/
```

### PHP ファイルの作成

```php:/var/www/index.php
<?php phpinfo(); ?>
```

ブラウザで確認して表示されれば完了です。

## 参考文献

* [PHP: FastCGI Process Manager (FPM) - Manual ](http://php.net/manual/ja/install.fpm.php)
* [Common Gateway Interface (CGI) - Wikipedia](https://ja.wikipedia.org/wiki/Common_Gateway_Interface)
* [FastCGI - Wikipedia](https://ja.wikipedia.org/wiki/FastCGI)
* [PHPのCGI版とモジュール版の違い | ServerKurabe](http://serverkurabe.com/php-cgi-module/)
* [apache の FastCGI(mod_fastcgi) + PHP-FPM で phpを動かしてみる(CentOS,ScientificLinux編) | レンタルサーバー・自宅サーバー設定・構築のヒント](http://server-setting.info/centos/php_cgi_module_exchange.html)
* [調べなきゃ寝れない！と調べたら余計に寝れなくなったソケットの話 - Qiita](http://qiita.com/kuni-nakaji/items/d11219e4ad7c74ece748)
* [PHP FastCGI Example | NGINX](https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/)