OXY NOTES

Nginxとリバースプロキシ、php-fpmとOPcacheのインストールと設定

WordPressを高速動作させるWebサーバの裏側

前回」に続いて「GMO VPSを契約してWordPressを安定動作させるまでのサーバ設定方法」第四弾。

この投稿ではWordPressの高速動作に最も効果のあるWebサーバのNginxリバースプロキシを組み合わせた設定と、PHPのphp-fpmOPcacheを組み合わせた設定をします。

今回の解説で利用したVPSはこちら


目次


Apacheのアンインストール

Nginxを導入するので、同じくWebサーバのApacheを削除しておきます。

# yum erase httpd
Dependencies Resolved

================================================================================
 Package           Arch        Version                      Repository     Size
================================================================================
Removing:
 httpd             x86_64      2.2.15-30.el6.centos         @updates      2.9 M
Removing for dependencies:
 httpd-manual      noarch      2.2.15-30.el6.centos         @updates      3.5 M
 mod_perl          x86_64      2.0.4-11.el6_5               @updates      6.1 M
 mod_ssl           x86_64      1:2.2.15-30.el6.centos       @updates      183 k
 mod_wsgi          x86_64      3.2-3.el6                    @base         177 k
 php               x86_64      5.3.3-27.el6_5               @updates      3.5 M
 webalizer         x86_64      2.21_02-3.3.el6              @base         324 k

Transaction Summary
================================================================================
Remove        7 Package(s)

Nginxの公式リポジトリを追加してインストール

Nginxは公式ページにリポジトリが用意されているので、CentOS 6用を登録してyumでインストールします。

nginx: Linux packages

# wget http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
# rpm -ivh nginx-release-centos-6-0.el6.ngx.noarch.rpm
# rm nginx-release-centos-6-0.el6.ngx.noarch.rpm

リポジトリの優先順位を変更

# vi /etc/yum.repos.d/nginx.repo

[nginx]の後にpriority=2を追加。

[nginx]
priority=2

以下を変更。

enabled=1
↓
enabled=0

追加したnginxリポジトリを利用してインストール

# yum --enablerepo=nginx install nginx
Dependencies Resolved

================================================================================
 Package        Arch            Version                    Repository      Size
================================================================================
Installing:
 nginx          x86_64          1.6.0-1.el6.ngx            nginx          335 k

Transaction Summary
================================================================================
Install       1 Package(s)

バージョン確認

# nginx -v
nginx version: nginx/1.6.0

リバースプロキシとログファイル用のディレクトリの作成と所有権の変更

キャッシュとログのディレクトリの所有権をNginxの実行ユーザーと合わせておきます。今回はTera Termのときに作成した「wwwユーザー」で管理します。

具体的には以下のディレクトリがなければ作成します。

$ mkdir /var/tmp/nginx
$ mkdir /var/cache/nginx
$ mkdir /var/log/nginx/
$ mkdir /var/lib/nginx

所有権をNginxの実行ユーザーに変更。

# chown -R www:www /var/tmp/nginx
# chown -R www:www /var/log/nginx/
# chown -R www:www /var/cache/nginx
# chown -R www:www /var/lib/nginx

Nginxの動作確認

# /etc/init.d/nginx start

とりあえずこれだけでブラウザにIP(名前解決できていればドメイン名)を入力すれば以下のように表示されるはずです。

Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. Further configuration is required.

For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.

Thank you for using nginx.

自動起動を有効にしとく

# chkconfig --list nginx
# chkconfig nginx on

Nginxのリバースプロキシを使った設定

下準備ができたので、Nginxの設定を行います。
ポート80で受けてキャッシュが有効でない場合は8080へ渡すという、基本的なリバースプロキシの使い方です。
その他の設定はそれぞれのコメントで解説したので参照してください。

モバイル用と、スマホ用でサイトの構成が異なる場合は113~120行目と、133行目のコメントアウトを削除、134行目を削除してください。

# cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.org
# vi /etc/nginx/nginx.conf
# 実行ユーザーの変更
user  www www;

# プロセス数。CPU数とすることが多いがnginxに任せる
worker_processes  auto;

# デフォルトはerrorレベル以上のログをlogs/error.log。この例ではwarn以上
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024; # 1つのworkerプロセスが開ける最大コネクション数
    multi_accept on; # できるだけクライアントからのリクエストを受け取る
    use epoll; # Linuxカーネル2.6以上の場合はepoll、BSDの場合kqueue
}

http {
    server_tokens off; # エラー画面にバージョンを表示しない
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    client_max_body_size 10M; # アップロードサイズ 413 Request Entity Too Large対策

    # 「nginx: [emerg] could not build the server_names_hash, you should increase server_names_hash_bucket_size: 32」の対策
    server_names_hash_bucket_size 64;

    # 「an upstream response is buffered to a temporary file」 対策
    # バッファサイズをオーバーしたのでメモリではなくファイルへ書き込むという警告。即問題があるわけではない
    proxy_max_temp_file_size 1024M;
    proxy_buffers 16 32k;
    proxy_buffer_size 64k;

    # 「a client request body is buffered to a temporary file」 対策
    client_body_buffer_size 64k; # Default: 8k|16k

    # logフォーマットの定義と指定。デフォルト(省略)はcombinedという定義。
    # こちらはhttpディレクティブ限定なので注意。
    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; # ハードディスクio処理とsocket-io処理のバランスを取る
    tcp_nopush      on; # TCP_CORKソケットオプションを使ってデータを1度に送る

    keepalive_timeout  65;

    gzip on; # コンテンツの圧縮を許可
    gzip_disable "MSIE [1-6]\."; # IEの1~6はキャッシュ対応していないので無効
    gzip_proxied any; # 全てのプロキシも圧縮
    gzip_min_length 1024; # gzip 圧縮を行うデータの最小サイズ
    gzip_comp_level 6; # 圧縮レベル設定、1-9
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript; # 圧縮ファイルタイプ

    # ここからプロキシサーバ設定(キャッシュサイズ、期間はサイトに合わせて変更すること)
    proxy_cache_path  /var/cache/nginx levels=1:2 keys_zone=czone:8m max_size=5000m inactive=7d; # キャッシュの場所、ディレクトリ構造、キーゾーン名とオブジェクトを保存するキーの全体サイズ(オブジェクト1つにつき128バイト消費する)、合計の最大キャッシュサイズ、保存期間。
    proxy_temp_path   /var/tmp/nginx; # テンポラリファイルのパス
    proxy_cache_key $scheme://$host$request_uri; # キャッシュに付与されるキー。削除時重要 ※要複数サイト運営時確認
    # ここからヘッダ。
    proxy_set_header  Host               $host;
    proxy_set_header  X-Real-IP          $remote_addr;
    proxy_set_header  X-Forwarded-Host   $host;
    proxy_set_header  X-Forwarded-Server $host;
    proxy_set_header  X-Forwarded-For    $proxy_add_x_forwarded_for;

    # バックエンドサーバ(リバースプロキシ)を指定する
    # ip_hashはローカルのみなので必要がないが一応作法として残す
    upstream backend {
        ip_hash;
        server 127.0.0.1:8080;
    } # upstream backend


# example.comの設定
#---------------------------------------------------------------------

    # まずはURLの正規化でwwwありをwwwなしへリダイレクト
    server {
        listen 80;
        server_name www.example.com;
        rewrite	^ http://example.com$request_uri?;
    }

    # リバースプロキシサーバの設定(ポート80用の設定)
    # 通常外部からのアクセスはここで受ける
    server {
        listen       80;
        server_name  example.com;

        # 現行のNginxでドキュメントルートをlocationディレクティブ内に記述するのは誤り
        root   /var/www/html/example;  # ドキュメントルート
        index  index.html index.htm index.php; # indexの設定

        access_log  /var/log/nginx/example.com_access.log  main;
        error_log  /var/log/nginx/example.com_error.log warn;

        # キャッシュの有効無効を調べるX-Cache:HITで有効、MISSで無効
        add_header X-Cache $upstream_cache_status;

        # ロケーション(以下の様な場合にどこで待ち受けるという指定)
        location /wp-admin { proxy_pass http://backend; }
        location ~ .*\.php { proxy_pass http://backend; }

        # サーバ全般の指定
        location / {
            # モバイル、スマホ、ログイン画面、それぞれフラグを付け
            # キャッシュの挙動を変える
            # ※モバイル用はサイトを分けていないのでキャッシュの振り分けは不採用

            # まずは初期化(using uninitializedと出るので)
            set $mobile "";
            set $do_not_cache "";

#            # モバイル用
#            if ($http_user_agent ~* '(DoCoMo|J-PHONE|Vodafone|MOT-|UP\.Browser|DDIPOCKET|ASTEL|PDXGW|Palmscape|Xiino|sharp pda browser|Windows CE|L-mode|WILLCOM|SoftBank|Semulator|Vemulator|J-EMULATOR|emobile|mixi-mobile-converter)') {
#                set $mobile 1;
#            }
#            # スマホ用
#            if ($http_user_agent ~* '(iPhone|iPod|Opera Mini|Android.*Mobile|NetFront|PSP|BlackBerry)') {
#                set $mobile 2;
#            }

            # WordPressのログイン画面用
            if ($http_cookie ~* "comment_author_[^=]*=([^%]+)%7C|wordpress_logged_in_[^=]*=([^%]+)%7C") {
                set $do_not_cache 1;
            }

            # ログイン画面の時はproxy_no_cacheやproxy_cache_bypassが1となりキャッシュが無効になる
            proxy_no_cache     $do_not_cache;
            proxy_cache_bypass $do_not_cache;
            proxy_cache czone;

            # プロキシキャッシュをMD5で作成する際に利用するキー
            # proxy_cache_key $scheme://$host$uri$is_args$args$mobile; #モバイル用で振り分ける場合はこちら
            proxy_cache_key $scheme://$host$uri$is_args$args;

            # ステータスコードごとにキャッシュの期間を指定。proxy_cache_pathよりこちらが優先
            proxy_cache_valid  200 301 302 1d; # 1日も保存すればいいかな?
            proxy_cache_valid  404 5m;
            proxy_cache_use_stale  error timeout invalid_header updating
                                   http_500 http_502 http_503 http_504;

            proxy_pass http://backend; # プロキシの保存先を指定

            # レスポンスヘッダの書き換え
            proxy_redirect http://example.com:8080/ /;
        } # location /

    } # server 80

    # バックエンドサーバの設定(こちらはポート8080の設定)
    server {
        listen       8080;
        server_name  example.com;

        root   /var/www/html/example;
        index  index.html index.htm index.php;

        location / {
            charset utf-8;

            # PHPに関する設定
            location ~ \.php$ {
                fastcgi_pass   unix:/var/run/php-fpm/php-fpm.sock;
                fastcgi_index  index.php;
                fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include        fastcgi_params;
            } # location ~ \.php$

            # 制限事項は別ファイルのものを読み込む
            include global/restrictions.conf;

            # WordPressも別ファイルのものを読み込む
            # 複数WordPressサイトを運営していても共通の部分はモジュール化するほうが管理が楽なため
            include global/wordpress.conf;
            # include global/wordpress-ms-subdir.conf; # マルチサイト用
            # include global/wordpress-ms-subdomain.conf; # マルチサイト用

            # バックエンドサーバ側でもIPを表示できるようにする。
            set_real_ip_from 127.0.0.1;
            real_ip_header X-Forwarded-For;

        } # location /

        # エラーページ用の設定
        error_page  404              /404.html;
        location = /404.html {
            root   /usr/share/nginx/html;
        } # location = /404.html

        # エラーページの設定500番代
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        } # location = /50x.html

    } # server 8080
} # http

restrictions.confの作成

上の設定ファイルでincludeしていたファイルの作成。
参考URLにあるようにglobalディレクトリを作成してファイルを作成します。

参考URL:Nginx « WordPress Codex

# mkdir /etc/nginx/global
# vi /etc/nginx/global/restrictions.conf
# Global restrictions configuration file.
# Designed to be included in any server {} block.</p>
location = /favicon.ico {
	log_not_found off;
	access_log off;
}

location = /robots.txt {
	allow all;
	log_not_found off;
	access_log off;
}

# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~ /\. {
	deny all;
}

# Deny access to any files with a .php extension in the uploads directory
# Works in sub-directory installs and also in multisite network
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~* /(?:uploads|files)/.*\.php$ {
	deny all;
}

wordpress.confの作成

これはWordPress独自の設定。こちらも参考URLのものを利用。

# vi /etc/nginx/global/wordpress.conf

fastcgi_passの部分だけサイトに合わせて設定します。
後でphp-fpmの項目で設定するので以下のようにしておいてください。

# WordPress single blog rules.
# Designed to be included in any server {} block.

# This order might seem weird - this is attempted to match last if rules below fail.
# http://wiki.nginx.org/HttpCoreModule
location / {
	try_files $uri $uri/ /index.php?$args;
}

# Add trailing slash to */wp-admin requests.
rewrite /wp-admin$ $scheme://$host$uri/ permanent;

# Directives to send expires headers and turn off 404 error logging.
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
	expires 7h;
	log_not_found off;
}

# Uncomment one of the lines below for the appropriate caching plugin (if used).
#include global/wordpress-wp-super-cache.conf;
#include global/wordpress-w3-total-cache.conf;

# Pass all .php files onto a php-fpm/php-fcgi server.
location ~ \.php$ {
	# Zero-day exploit defense.
	# http://forum.nginx.org/read.php?2,88845,page=3
	# Won't work properly (404 error) if the file is not stored on this server, which is entirely possible with php-fpm/php-fcgi.
	# Comment the 'try_files' line out if you set up php-fpm/php-fcgi on another machine.  And then cross your fingers that you won't get hacked.
	try_files $uri =404;

	fastcgi_split_path_info ^(.+\.php)(/.+)$;
	#NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

	include fastcgi_params;
	fastcgi_index index.php;
	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
#	fastcgi_intercept_errors on;
#	fastcgi_pass 127.0.0.1:9000;
	fastcgi_pass   unix:/var/run/php-fpm/php-fpm.sock;
}
ここでnoteに「cgi.fix_pathinfo = 0」にしろとあるが、PHP 5.3.9以降なら必要ない。security.limit_extensionsという項目があり、デフォルト値がphpとなっているため、phpファイル以外をphpとして実行するようなことはないため。

参考URL:PHP-FPMでcgi.fix_pathinfo=0は必要なのか

nginxで動作させるphp-fpmのインストールと設定

過去はspawn-fcgiとの組み合わせが良くありましたが、最近はphp-fpmを使うのが主流です。

Nginx でPHPを動かす(php-fpmをバイナリパッケージ(rpm)を作成し、インストールする) [PHP5.2系の場合]」によると、
spawn-fcgiが、PHPと全く独立したプログラムで、FastCGIプロセス管理を行うのに対して
php-fpmは、PHPに一部機能を組み込んで、FastCGIプロセス管理を行うという違いがあるそうです。

# yum install php-fpm
Dependencies Resolved

================================================================================
 Package         Arch           Version                   Repository       Size
================================================================================
Installing:
 php-fpm         x86_64         5.3.3-27.el6_5            updates         1.1 M

Transaction Summary
================================================================================
Install       1 Package(s)

php-fpmの設定ファイルを編集

# cp /etc/php-fpm.d/www.conf /etc/php-fpm.d/www.conf.org
# vi /etc/php-fpm.d/www.conf

一応listenの値を確認しておく。nginxの設定と合わせる。

listen = 127.0.0.1:9000
↓
listen = /var/run/php-fpm/php-fpm.sock

リバースプロキシを導入して、w3c total cacheで管理したいので、キャッシュの削除の際に権限が無いと困るのでxginxの実行者と統一しておく。

user = apache
group = apache
↓
user = www
group = www

listenについては別に設定する必要がある。

;listen.owner = nobody
;listen.group = nobody
;listen.mode = 0666
↓
listen.owner = www
listen.group = www
listen.mode = 0660

メモリ等が不足する場合は実行数を以下の設定で調整する。

pm.max_children = 50
pm.max_spare_servers = 35
↓
pm.max_children = 15
pm.max_spare_servers = 15
pm.max_spare_servers」は「pm.max_children」以下にしないと、下のエラー出るので注意。

Starting php-fpm: [16-Sep-2014 10:39:03] ALERT: [pool www] pm.min_spare_servers(5) and pm.max_spare_servers(35) cannot be greater than pm.max_children(15)
[16-Sep-2014 10:39:03] ERROR: failed to post process the configuration
[16-Sep-2014 10:39:03] ERROR: FPM initialization failed

ログ用のディレクトリの権限変更

デフォルトだとapacheになっているようだ。

# chown www.www /var/log/php-fpm/

php-fpmの起動

# /etc/init.d/php-fpm start

php-fpmを起動項目に追加

# chkconfig --list php-fpm
# chkconfig php-fpm on

/etc/php.iniの編集

このままだとdate.timezoneのエラーが出るのでPHPのタイムゾーンの設定をする。

# cp /etc/php.ini /etc/php.ini.org
# vi /etc/php.ini
;date.timezone
↓
date.timezone = Asia/Tokyo

このままだとアップロード可能な最大ファイルサイズが2MBなのを修正。どちらも「memory_limit = 128M」以下でないとだめなので注意。

; post_max_size = 8M
↓
post_max_size = 32M
; upload_max_filesize = 2M
↓
upload_max_filesize = 32M

これで32MBまでアップロードできる。

逆にPHPの実行にメモリが足りないと出ることがあるのでphp-fpmの設定ファイルを調整。

# vi /etc/php-fpm.d/www.conf
;php_admin_value[memory_limit] = 128M
↓
php_admin_value[memory_limit] = 256M

再起動して反映。

# /etc/init.d/php-fpm restart

OPcacheのインストールと設定

PHPのキャッシュといえばAPCでしたが、深刻なメモリ上の障害があり、バージョン3.1.14から開発が中止しています。
代わりにPHP5.5からはopcasheが正式に採用されました。
導入が容易で、ベンチマークはOPcacheのほうが20%も高速とのことなので、OPcacheへの乗り換えもすぐに終了しそうです。

今回インストールしたremiリポジトリではPHPのバージョンが5.4止まりでしたが、PECLを利用したインストール方法が用意されています。

PECLについては詳しくは「PECL 拡張モジュールのインストール」を参照してください。

PECLを使うにはphp-develが必要なのでインストールします。

# yum install --enablerepo=remi --disablerepo=base,updates,extras,centosplus,contrib php-devel
(省略)
Dependencies Resolved

===============================================================================
 Package             Arch          Version                   Repository   Size
===============================================================================
Installing:
 php-devel           x86_64        5.4.32-1.el6.remi         remi        1.2 M
Updating for dependencies:
 php-bcmath          x86_64        5.4.32-1.el6.remi         remi         61 k
 php-cli             x86_64        5.4.32-1.el6.remi         remi        3.9 M
 php-common          x86_64        5.4.32-1.el6.remi         remi        933 k
 php-fpm             x86_64        5.4.32-1.el6.remi         remi        1.3 M
 php-gd              x86_64        5.4.32-1.el6.remi         remi        145 k
 php-mbstring        x86_64        5.4.32-1.el6.remi         remi        946 k
 php-mcrypt          x86_64        5.4.32-1.el6.remi         remi         52 k
 php-mysql           x86_64        5.4.32-1.el6.remi         remi        137 k
 php-pdo             x86_64        5.4.32-1.el6.remi         remi        121 k
 php-recode          x86_64        5.4.32-1.el6.remi         remi         38 k
 php-tidy            x86_64        5.4.32-1.el6.remi         remi         56 k
 php-xml             x86_64        5.4.32-1.el6.remi         remi        172 k

Transaction Summary
===============================================================================
(省略)

peclでOPcacheをインストール

# pecl install zendopcache-beta
(省略 ズラーっとたくさん出る)
Build process completed successfully
Installing '/usr/lib64/php/modules/opcache.so'
install ok: channel://pecl.php.net/zendopcache-7.0.3
configuration option "php_ini" is not set to php.ini location
You should add "zend_extension=opcache.so" to php.ini

丁寧に「zend_extension=opcache.so」を追加しろと表示されています。

# vi /etc/php.ini

特に設定する項目はないですが、opcache.memory_consumptionで使用するメモリサイズ(MB)を指定してください。
WordPress単独なら128MBもあれば十分です。

[zendopcache]
zend_extension=/usr/lib64/php/modules/opcache.so
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1

再起動

# /etc/init.d/php-fpm restart

確認

# php -v
PHP 5.4.32 (cli) (built: Aug 21 2014 07:33:35)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2014 Zend Technologies
    with Zend OPcache v7.0.3, Copyright (c) 1999-2014, by Zend Technologies

Zend OPcache v7.0.3」と表示されていれば成功です。


OPcacheの利用状況をグラフ化して表示

インストールしただけだと正しく動作しているか、容量が十分かなど確認できないので、グラフ化してくれるツールをインストールします。

OCP – Opcache Control Panel」をダウンロードして、公開ディレクトリにアップロード。
あとはブラウザからアクセスするだけで以下のように表示されます。
常設する場合は、セキュリティ上の問題になるので、アクセス規制をしておいてください。

Free Memoryの項目が極端に少ないようならopcache.memory_consumptionを、
キーの数が足りないようならopcache.max_accelerated_filesを、
それぞれメモリの許す範囲で増やしてください。


今回の解説で利用したVPSはこちら
GMOクラウドのVPS

以上でWebサーバとPHPの設定が完了しました。
適切に設定をすれば、無調整のApacheで運用するのに比べて数百倍のアクセスをさばくことができます。

次は「MySQLのインストールと各種設定、phpMyAdminのインストールと設定」です。