サーバー

VPS NGINXとPHPサーバーのインストール

本稿はNGINXでPHPを利用する設定などの記事ですが、NGINXやCGIの利用は初めてなので内容に誤りがあった場合はお許しください。

PHP-FPMについて

ApacheではPHPを動作させる方法が2種類、「モジュールタイプ」と「CGIタイプ」があります。違いは動作させる仕組みにあり、それにより実行時の動作権限が異なり、ファイルパーミッションなどセキュリティに影響が生じます。

モジュールタイプはApacheの動作権限を継承し、CGIタイプはPHPを動作させるユーザー権限となります。NGINXではモジュールタイプで動作するPHPがありませんので、CGIタイプで動作させることになります。

ところでCGIタイプはアクセスごとにプロセスが起動されるため、OSによるオーバーヘッド時間が大きくなり、アクセス数が多いと動作に支障が出るため、FastCGIと呼ばれる機能が用意されました。

FastCGIは一度起動されるとプロセスが残り、次のリクエストでそれが利用されることにより、負荷の低い時間短縮な動作が可能となります。

PHP-FPM(FastCGI Process Manager)はFastCGIを実装したサーバープログラムの1つを指し、高い負荷が掛かるサイトに有用な機能を追加したものです。

なおCGI(Common Gateway Interface)は、Webサーバーでプログラムを動作させる仕組みのことを言います。

PHPのインストール

インストールする「PHP-FPM」はremiレポジトリ にあるので、インストールに先立ちレポジトリ を確認、必要があれば追加してPHPのインストールを実行します。

# yum repolist all

# rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

# yum install --enablerepo=remi,remi-php71 php-fpm php-cli php-common php-devel php-gd php-mbstring php-mysqlnd php-pdo php-soap php-xml php-xmlrpc

インストールしたPHPのバージョンを「php -v」で確認したところ、「PHP 7.1.17」でした。

NGINXやPHP-FPMはデーモンの状態で動作します。以下はOS起動時の自動起動設定の確認、PHP-FPMの起動と自動起動設定です。

# systemctl list-unit-files -t service

# systemctl start php-fpm.service

# systemctl enable php-fpm.service

サンプルプログラムとして以下の内容の「hello.php」プログラムファイルを用意し、ドキュメントルートディレクトリ「/usr/share/nginx/html」に置きます。

<?php
    echo 'Hello, NGINX PHP-FPM';

以降はNGINXとPHPの設定ファイルの内容についてですが、動作に関して両方の設定に共通した項目があります。

それはWebサーバー(NGINX)のアクセスから指定されたPHPプログラムが実行されるまでの流れに関して、URLでPHPのスクリプトファイルが指定されたとき、NGINXは「ソケット」を利用してPHP-FPMにスクリプトファイル名を知らせ、PHP-FPMはそのプログラムを読み込んで実行することです。

この流れを実現するためそれぞれの設定ファイル、「/etc/nginx/conf.d/default.conf」と「/etc/php-fpm.d/www.conf」に適切な「ソケット」の情報を設定します。

NGINXの設定

「/etc/nginx/conf.d/default.conf」ファイルを設定します。

server {
    listen      80;
    server_name localhost;
    location / {
        root  /usr/share/nginx/html;
        index index.html index.htm;
    }
    error_page  500 502 503 504  /50x.html;
    location = /50x.html {
        root  /usr/share/nginx/html;
    }
    location ~ \.php$ {
        root          /usr/share/nginx/html;
        fastcgi_pass  127.0.0.1:9000;
        fastcgi_index index.php;
        include       fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

サイトをアクセスした時のドキュメントルートは、インストール時「/usr/share/nginx/html」がデフォルトで指定されています。

PHPに関する設定は「location ~ \.php$」セクションですが、インストール直後はコメントアウトされていたので、上記のように設定しました。

この中で「root」の指定と「fastcgi_param」の設定によりスクリプトファイルが読み込まれるため、設定が正しくない場合は「File not found」が表示されることがあります。

ソケットは「fastcgi_pass」項目にローカルループバックアドレスの9000番ポートが指定されていたので、それをそのまま利用します。

PHP-FPMの設定

「/etc/php-fpm.d/www.conf」が設定ファイルですが、操作した部分だけ記述します。

listen = 127.0.0.1:9000

がコメントアウトされていたので、コメントを外しました。

プログラム実行時のユーザーとグループ権限について、「user」と「group」項目に設定できます。これは今後、動作に合わせて重要な設定になりますが、今はデフォルトのまま「apache」にしておきます。

動作はそれぞれのサービスを再起動して、ブラウザで「http://サイトURL/hellop.php」をアクセスして確認してください。

# systemctl restart nginx.service
# systemctl restart php-fpm.service

ブラウザーで「http://サイトのURL/hello.php」を指定し、先に用意したテストプログラムの動作を確認します。

動作環境の準備

NGINXやPHP-FPMなど一連の動作確認ができたので、以降は実際に利用する状況に合わせて設定を実施します。

先ず、ファイルアクセスはWordPressのアップデートや、FTPSによるアップロードがあるので、共通グループ「webuser」を用意します。

# groupadd webuser

自分のログインIDとNGINXをグループに追加します。

# usermod -aG webuser ユーザーID
# usermod -aG webuser nginx

サイトのドキュメントルートは、非暗号化アクセスの場合「/usr/share/nginx/html」、暗号化アクセスの場合「/usr/share/nginx/www」とします。

ディレクトリのグループを「webuser」とし、ファイルパーミッションはオーナー・グループが「rw」、その他ユーザーは「r」、そして下位層ではグループ「webuser」を引き継ぐようにします。

# cd /usr/share/nginx
# chgrp webuser html
# chmod g+s html
# mkdir www
# chgrp webuser www
# chmod g+s www

次の記事で話題にしますが、この設定だけではWordPressの自動アップデートができず、FTP情報を求められてしまいました。

NGINX SSLとUNIXドメインソケットの設定

NGINXからPHP-FPMでプログラムを動作させる場合、UNIXドメインソケットを利用した方がループバックアドレスのソケットより動作が早い、と言われているのでそちらに切り替えます。

余談ですがソケットについて、通信におけるOSI参照モデルという7階層の中でループバックアドレスを使う場合は上から4番目のトランスポート層で、UNIXドメインは3番目のセッション層で処理するため、オーバーヘッド処理が少なくなるようです。

参考:調べなきゃ寝れない!と調べたら余計に寝れなくなったソケットの話

以下は、NGINXで非暗号化接続された場合の「/etc/nginx/conf.d/default.conf」のlocationセクションの設定内容です。

location ~ \.php$ {
    root          /usr/share/nginx/html;
    fastcgi_pass  unix:/var/run/php-fpm/php-fpm.socket;
    fastcgi_index index.php;
    include       fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

次に、SSLサイトのための設定です。

ここの設定内容は、さくらのVPSで用意されたセットアップスクリプトを利用することで、取得された証明書ファイルなどのファイルパスが設定されていました。FTPSの利用はこの証明書ファイルを使いました。

注)設定ファイル内の「ドメイン」は、encrypt sslを取得するため指定したドメイン(例:www.example.com)を示します。

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}
server {
    listen 443 ssl http2;
    server_name ドメイン;

    location / {
        root /usr/share/nginx/www;
        index index.html index.htm;
    }

    ssl_protocols TLSv1.2;
    ssl_ciphers EECDH+AESGCM:EECDH+AES;
    ssl_ecdh_curve prime256v1;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;

    ssl_certificate /etc/letsencrypt/live/ドメイン/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ドメイン/privkey.pem;

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/www;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        root /usr/share/nginx/www;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

サイトにSSL接続するドキュメントルートは「/var/share/nginx/www」ディレクトリで、上述の通りディレクトリを用意しました。

UNIXドメインソケットの指定は「location」セクションにおいて

unix:/var/run/php-fpm/php-fpm.sock

を指定します。このファイルは次のPHP-FPMの設定ファイルで設定、PHP-FPMを再起動することで「/run/php-fpm/php-fpm.sock」ファイルが生成されます。そしてPHP-FPMを停止すると「/var/run/php-fpm」ディレクトリが削除されます。

なおこのファイルはnginxプロセスがアクセスできるように、PHP-FPMの設定ファイル内でオーナーユーザーとグループの指定をします。

以下、PHP-FPMの設定ファイル「/etc/php-fpm.d/www.conf」で、[www]セクションに以下の項目を設定します。

user = nginx
group = nginx
listen = /var/run/php-fpm/php-fpm.sock
listen.owner = nginx
listen.group = nginx

PHP-FPMを再起動するとUNIXドメインソケットのファイルが、設定したファイルパスに生成されます。設定ファイルの「listen.owner」と「listen.group」の設定が、このファイルのオーナーとグループになります。この指定がないとPHPファイルを指定した時NGINXでエラーが出力されます。

php-fpm.sockファイルのパーミンションは先頭のファイルタイプに「s」が表示され、Socketであることが示されます。

ベーシック認証とPHP

Apacheでは、特定のディレクトリアクセスに対してベーシック認証を簡単に設定できました。が、NGINXでは苦労したので、とりあえず動作したNGINXの設定ファイルの内容を後で見直すためにメモっておきます。

最初にベーシック認証で利用するID、パスワードの登録ファイル(Apacheは.htpasswdファイル)を用意しますが、NGINXのデフォルトの設定では不可視ファイルもアクセスできるため、ドキュメントルート配下と異なる場所に置くようにしました(ここではドキュメントルートの上位ディレクトリ「/usr/share/nginx」にしました)。

ファイルの内容はApacheと同様なので、Apacheで利用するhtpasswdコマンドが利用できます。が、そのためだけにインストールするのは避けて、opensslを使った方法が検索で見つかったのでそれを利用しました。

opensslでパスワードを生成するには、「openssl passwd -apr1 パスワード」を実行します。パスワードファイルは1行に「ユーザーID:パスワードデータ」で、これをパスワードファイルに追加(もしくは新規作成)するため、teeコマンドを利用します。

# echo "ユーザーID:$(openssl passwd -apr1 パスワード) | tee -a /usr/share/nginx/htpasswd

次にhttpsアクセスにおいて、ベーシック認証を必要とするサブディレクトリ「basic」(説明で利用するためのディレクトリです)に対して、そのディレクトリにあるPHPプログラムをURLに指定した際PHPが動作するように、NGINXの設定ファイルを変更します。

注)設定ファイル内の「ドメイン」は、Let’s Encryptで登録したドメイン(例:www.example.com)を示します。

map $http_upgrade $connection_upgrade {
  default upgrade;
  '' close;
}
server {
  listen 443 ssl http2;
  server_name ドメイン;

  location / {
    root /usr/share/nginx/www;
    index index.html index.htm;
  }

  ssl_protocols TLSv1.2;
  ssl_ciphers EECDH+AESGCM:EECDH+AES;
  ssl_ecdh_curve prime256v1;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  ssl_certificate /etc/letsencrypt/live/ドメイン/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/ドメイン/privkey.pem;

  error_page 500 502 503 504 /50x.html;
  location = /50x.html {
    root /usr/share/nginx/www;
  }

  location ~ /basic/ {
    root /usr/share/nginx/www;
    auth_basic "MTSystems Auth";
    auth_basic_user_file /usr/share/nginx/htpasswd;
    location ~ \.php$ {
      fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
      fastcgi_index index.php;
      include fastcgi_params;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
  }

  # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
  #
  location ~ \.php$ {
    root /usr/share/nginx/www;
    fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }
}