nginx + apache + php-fpmで作る2024年版Webサーバー
2024年現在、Webサーバーはnginxが最もメジャーなサーバーとなっているようですね。
弊社のローカルネットワーク上にある開発環境もapacheから脱皮すべく、nginxをベースにすることになりましたが、現在運用中のサイトで使われているphpコードや.htaccessが全く使えなくなってしまう環境への移行はまだ時期尚早かと思います。
著名なレンタルサーバーがnginxオンリーではなく、nginx + apacheであるために、nginxで調整してしまうと一般的なレンタルサーバーでは問題が発生すること間違いなし。
ということで、nginx + apache + php-fpmサーバーを作成し、バーチャルホストのconfigでどのように処理するかを設定してあげることで混在状態にあるwebサーバーの仕様に合わせやすいようにします。
このサーバーを構築するにあたり、大きく躓いたところが4つあります。
- サーバー起動時、apacheのサービス起動速度が遅く、nginxが起動してきたときにバックエンドで受けるapacheのポートが見つからないのでnginxが正常起動しない。
- デーモン起動時に名前解決が必要な記述を行うと、条件によってはnginxが起動できずエラーとなる。
- 構築したサーバーにiOSでhttpsアクセスを行うと、「ページを開けません。ネットワーク接続が切れました。」となる。通信途中でiOSが接続を切ってしまい、表示されない。
- mod_rewriteの状況によっては、httpとhttpsのポート間で無限ループに陥ってサイトが表示されない。
OSはRockyLinux9.2です。
データ共有の為のsambaやssh、SELinuxの無効化やfirewalldの停止などの基本設定は初期段階で済ませます。
/var/wwwをsambaで共有し、以下にバーチャルホスト用のデータディレクトリ、confディレクトリ、ログディレクトリ、TLS証明書ディレクトリを作成し、適切なファイルを配置します。
nginx conf: /var/www/conf.d/(virtualhostdomain).nginx.conf
apache conf: /var/www/conf.d/(virtualhostdomain).apache.conf
HTML: /var/www/vhosts/virtualhostdomain(バーチャルホストごとにディレクトリ作成)
ログ : /var/www/log/virtualhostdomain(バーチャルホストごとにディレクトリ作成)
TLS証明書 : /var/www/tls/以下にワイルドカード自己署名証明書
apache、nginx、php-fpmのインストールは先達の良い資料が有りますので、それを参考にします。
また、明記はしておりませんけども、設定変更後はデーモンの再起動を行ってくださいね。
[root@server ~]# httpd -v
Server version: Apache/2.4.57 (Rocky Linux)
[root@server ~]# nginx -v
nginx version: nginx/1.25.3
[root@server ~]# php -v
PHP 8.3.2 (cli) (built: Jan 16 2024 13:46:41) (NTS gcc x86_64)
[root@server ~]# mariadb
Server version: 11.2.2-MariaDB-log MariaDB Server
各ミドルウェアをインストールし、サーバー起動時に自動起動するようにします。
この時点では、まだバーチャルホストの設定が入っていないので、サービス起動時のエラーは出てきませんが、バーチャルホストの設定がいくつが入ってくると、受けるポートが無いとなること確実なので、まずはapacheが起動していないとnginxが起動しない、apache必須の指定をnginxの起動スクリプトに記述します。
また、各バーチャルホスト設定時にバックエンドに投げるURLの書き方でも起動エラーの原因になります。
[root@server ~]# vim /usr/lib/systemd/system/nginx.service
[Unit]
Description=nginx - high performance web server
Documentation=http://nginx.org/en/docs/
#最後に httpd.service を追加
After=network-online.target remote-fs.target nss-lookup.target httpd.service
Wants=network-online.target
#新規追加でhttpd.service を追加
Requires=httpd.service
そして、先達の資料通りに nginxはhttpに80番とhttpsに443番、バックエンドで動くapacheにはhttpは8080番、httpsは8443番を割り当て、その他設定はお好みに行います。
[root@server ~]# vim /etc/httpd/conf/httpd.conf
Listen 80
↓
Listen 8080
#バーチャルホストconfディレクトリを追加
IncludeOptional /var/www/conf.d/*.apache.conf
[root@server ~]# vim /etc/httpd/conf.d/ssl.conf
Listen 443 https
↓
Listen 8443 https
以下を /etc/nginx/nginx.confのhttpセクションに追記します。
proxy_hide_headerはiOSからアクセスしなければ問題ないのですが、様々なデバイスへの対応をチェックする開発環境なので必要かと思います。
SSL/TLS証明書は開発用ローカルドメインのワイルドカードゆえに大元で設定しています。
ドメインごと別に指定したいときは各バーチャルホストのconfigに記述すればOKです。
[root@server ~]# vim /etc/nginx/nginx.conf
http {
# バーチャルホストconfディレクトリを追加
include /var/www/conf.d/*.nginx.conf;
# 最大ボディサイズ
# php.ini post_max_size と同じ値
client_max_body_size 256M;
client_body_buffer_size 2M;
# iOSがTLS接続を切ってしまうことへの対策
proxy_hide_header Upgrade;
# TLS証明書はご自分の環境に合わせて
ssl_certificate /var/www/tls/local.crt;
ssl_certificate_key /var/www/tls/local.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
# proxy動作時のバッファ量
proxy_temp_path /var/www/tmp/nginx;
proxy_max_temp_file_size 4096m;
proxy_buffer_size 16k;
proxy_buffers 16 16k;
proxy_busy_buffers_size 32k;
# php-fpm動作時のバッファ量
fastcgi_buffer_size 16k;
fastcgi_buffers 64 16k;
fastcgi_busy_buffers_size 32k;
fastcgi_temp_file_write_size 32k;
}
ここで、PHP-FPM動作時のバッファ量が既定値のままだとほぼ間違いなくwordpress管理画面でcgiキャッシュ不足となり、ディスクへの書き込みが発生します。
2024/02/14 11:03:06 [warn] 3739#3739: 1 an upstream response is buffered to a temporary file /var/cache/nginx/fastcgi_temp/1/00/0000000001 while reading upstream, client: 192.168.*.*
色々パターンを試してみましたが、自社環境ではfastcgi_buffers を例の程度に大きくすれば書き込みは発生しない模様です。
環境によるかと思いますので、適切な量を探してください。
次に、nginxとapacheがそれぞれphp-fpmを使えるようにします。
デフォルトのままだと、apacheユーザーがソケットを所有しており、nginxユーザーがphp-fpmに投げるソケットが有りませんのでnginx用のphp-fpmソケットを用意します。
/etc/php-fpm.d/以下にある www.confをコピーし、
/etc/php-fpm.d/apache.conf
/etc/php-fpm.d/nginx.conf
を作り、元のwww.confはwww.conf.orgなどにリネームして起動しないようにします。
/etc/php-fpm.d/apache.confはプール名ソケット名をwwwからapacheに変更します。
/etc/php-fpm.d/nginx.confはプール名とソケット名、ユーザーとグループをwwwからnginxに変更します。
各configに関して詳しい方は突き詰めて設定していただいても全然問題ありませんが、今回は分けて動けばいいのでファイルの該当文字列wwwの部分をエディタで検索してapacheもしくはnginxに置換する程度です。
例としてapache.confの主に重要な部分を記述します。
nginx.confはapacheの文字列をnginxに置き換えて記述してください。
ついでに、php-fpmのエラーログをドメインごとに吐き出せるように設定を変えておきます。
[root@server ~]# vim /etc/php-fpm.d/apache.conf
[www⇒apache]
user = apache
group = apache
listen = /run/php-fpm/www.sock ⇒ /run/php-fpm/apache.sock
;pmの設定値は開発用なのでスピードより安定を重視
pm = static
# pm.max_chlidrenはCPUコア数
pm.max_children = 4
pm.max_requests = 100
;以下、ドメインごとにエラーログを出力させるために変更できない既定値を解除し、代わりの既定値をセット
;php_admin_value[error_log] = /var/log/php-fpm/www-error.log
;php_admin_flag[log_errors] = on
php_value[error_log] = /var/log/php-fpm/apache-error.log
php_flag[log_errors] = on
apacheのPHPソケットはphp.confの最終部分にwww.sockとして記述されていますので、ここをプール名をapacheとしたapache.confに揃えます。nginxユーザーのソケットは後述しますがバーチャルホストの設定ファイルに記述します。
[root@server ~]# vim /etc/httpd/conf.d/php.conf
SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"
↓
SetHandler "proxy:unix:/run/php-fpm/apache.sock|fcgi://localhost"
php-fpmのエラーログですが、これ、デフォルトだと先のコンフィグに有ったように/var/log/php-fpm以下に出てしまいます。
通常は、.user.iniでログの出力先を指定しますが、これをこのまま本番環境にアップしてしまうことは必ず発生してしまうので、開発環境は別のファイルを参照するようにして設定値が本番環境に干渉しないようにします。
/etc/php.iniを変更して、.local.user.iniというファイルを参照するようにします。
[root@server ~]# vim /etc/php.ini
;;;;;;;;;;;;;;;;;;;;
; php.ini Options ;
;;;;;;;;;;;;;;;;;;;;
; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini"
;user_ini.filename = ".user.ini"
user_ini.filename = ".local.user.ini"
これで、個々のサービスの関係が築かれたので干渉せずに起動するはずです。
サービス関係の設定が終わったので、バーチャルホストの設定に入ります。
ここでは、各バーチャルホスト設定ごとにapache用とnginx用のコンフィグファイルを用意します。弊社が使用するVPSサーバーはPleskを使用しているので、Pleskのデフォルトに合わせておくとRewriteなどを追加する際に書き換えずに済みます。
domainの部分は設定するドメインが分かりやすいように適宜変更願います。
また、開発用という観点からHSTSの時間は長すぎるとテストの際に面倒が起きますので普段の運用ではありえない5分で設定しています。
[root@server ~]# vim /var/www/conf.d/domain1.nginx.conf
#nginxですべて処理する例
#-- domain1.local.cretbird.co.jp ---------------------------------------------
server {
listen 80;
server_name domain1.local.cretbird.co.jp;
#httpアクセスはnginxでhttpsに転送
return 301 https://domain1.local.cretbird.co.jp$request_uri;
}
server{
listen 443 ssl;
http2 on;
server_name domain1.local.cretbird.co.jp;
root /var/www/vhosts/domain1.tld;
index index.php index.html index.htm;
access_log /var/www/log/domain1.tld/ssl_access.nginx.log main;
error_log /var/www/log/domain1.tld/ssl_error.nginx.log warn;
add_header Strict-Transport-Security 'max-age=300';
location ~ \.php$ {
include /var/www/conf.d/php-fpm.inc;
}
# NGINX rewrite文をインクルード
include /var/www/vhosts/domain1.tld/rewrite.inc;
}
ここで、各セクションで同じ設定値を共用しているのでソースの冗長化を防ぐために共通部分を別ファイルにしてincludeします。
includeしているphp-fpm.incの内容は、
fastcgi_pass unix:/run/php-fpm/nginx.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
rewrite.incの内容は
#開発サーバーとPlesk環境で共通の Nginx Rewrite
if ($request_uri ~ "^./index.html$"){
rewrite "^/(.)/index.html$" /$1/ permanent;
}
if ($request_uri ~ "^./index.php$"){
rewrite "^/(.)/index.php$" /$1/ permanent;
}
として、適宜追加します。
この内容をPleskのNginx追加設定にペーストするか、直接インクルードしてしまってもOKです。
include /(web root ディレクトリ)/rewrite.inc;
次の例はwordpressなど .htaccess が複雑な場合に用いるとすんなり移行できます。
http⇒httpsリダイレクトをnginxでするとリダイレクト無限ループになる場合があるので、.htaccessでのhttp⇒httpsリダイレクトに任せます。
.htaccessを修正しても良いかと思いますが、現行本番サーバーと異なる値となってそのままアップしてしまえば事故の元です。
本当ならnginxの443番はapacheの8080番に投げれば暗号化処理をしないで済むのでさらに高速になるんですが、強制https動作するようwordpressなどの設定値を変える必要があり本番環境と違う設定になる可能性が高くなります。
[root@server ~]# vim /var/www/conf.d/domain2.nginx.conf
#wordpress動作に適したconfig例
#-- domain2.local.cretbird.co.jp ---------------------------------------------
server {
listen 80;
server_name domain2.local.cretbird.co.jp;
root /var/www/vhosts/domain2.tld;
index index.html;
access_log /var/www/log/domain2.tld/access.nginx.log main;
error_log /var/www/log/domain2.tld/error.nginx.log debug;
#静的ファイルは直接処理(wordpressで拡張子をhtmlにするプラグイン等拡張子コントロールに注意)
location ~ .*\.(html?|jpe?g|gif|png|svg|webp|css|js|inc|ico|inc|eot|ttf|woff|woff2|swf|mpg|mp4)$ {
break; # apache に投げない
}
#NGINX + APACHE&PHP-FPM
location / {
proxy_pass http://localhost:8080;
include /var/www/conf.d/proxy_set_header.inc;
}
}
server{
listen 443 ssl;
http2 on;
server_name domain2.local.cretbird.co.jp;
root /var/www/vhosts/domain2.tld;
index index.html;
access_log /var/www/log/domain2.tld/ssl_access.nginx.log main;
error_log /var/www/log/domain2.tld/ssl_error.nginx.log debug;
add_header Strict-Transport-Security 'max-age=300';
#静的ファイルは直接処理(wordpressで拡張子をhtmlにするプラグイン等拡張子コントロールに注意)
location ~ .*\.(html?|jpe?g|gif|png|svg|webp|css|js|inc|ico|inc|eot|ttf|woff|woff2|swf|mpg|mp4)$ {
break; # apache に投げない
}
#NGINX + APACHE&PHP-FPM
location / {
proxy_pass https://localhost:8443;
include /var/www/conf.d/proxy_set_header.inc;
}
}
includeしているproxy_set_header.incの内容は、
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
また、proxy_pass でサーバー名をlocalhostや127.0.0.1以外のDNSへの問い合わせが必要なサーバー名にすると、DNSとの通信が出来ない状況や名前解決できないときにnginxが起動できずエラーとなってしまいます。
実運用サーバでは、hostsに記述することで回避できます。
弊社ではローカル開発環境専用DNSを立てており、このDNSサーバーの起動が遅く、始業時に用意!ドン!で電源ボタンを押したらWebサーバーが先に立ち上がってDNSでの接続先の名前解決が出来ずにnginxだけが起動できなかったのです。
次はバックエンドapacheの設定です。
domain1.tld はnginxだけで動作しているので、バックエンドのapacheは設定不要です。
nginxでhttp⇒httpsのリライトを掛けていないのでこちらも両方受けることが出来るようにして、.htaccessに処理を任せます。
[root@server ~]# vim /var/www/conf.c/domain2.apache.conf
# wordpress動作に適したconfig例
# -- domain2.local.cretbird.co.jp ---------------------------------------------
<VirtualHost *:8080>
ServerName domain2.local.cretbird.co.jp
DocumentRoot /var/www/vhosts/domain2.tld
DirectoryIndex index.php index.html
ErrorLog /var/www/log/domain2.tld/error.apache.log
CustomLog /var/www/log/domain2.tld/access.apache.log realip
</VirtualHost>
<VirtualHost *:8443>
ServerName domain2.local.cretbird.co.jp
DocumentRoot /var/www/vhosts/domain2.tld
DirectoryIndex index.php index.html
Protocols h2 http/1.1
ErrorLog /var/www/log/domain2.tld/ssl_error.apache.log
CustomLog /var/www/log/domain2.tld/ssl_access.apache.log realip
SSLEngine on
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW
SSLCertificateFile /var/www/tls/local.crt
SSLCertificateKeyFile /var/www/tls/local.key
Header set Strict-Transport-Security "max-age=300"
</VirtualHost>
設定したドメインが表示されるかを確認しますが、たぶんこれではエラーでapacheが起動しないと思います。
nginxからのリクエストを受ける際、IPアドレスがローカルサーバーのIPとなりクライアントIPが記録されないのでCustomLogでrealipという名前を作っています。
[root@server ~]# vim /etc/httpd/httpd.conf
LogFormat "%{X-Real-IP}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" realip
LogFormat "%{X-Forworded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" forworded
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
ここまで。
適宜その他logrotateの設定をしてくださいね。
これは単機での設定例なので、nginxフロントエンド1台+php-fpm2台+データ用NFSサーバー1台+DBサーバー1台なんて構成も柔軟に出来るかな?なんて思っています。