FreeBSD14 + Nginx + PHP8.3 + WordPress 6.8.2で起きた原因不明のPHPエラー

突然PHPエラーでサイトが表示されなくなって調査しているときの覚書。
Gemini 2.5 Proと対話

環境: FreeBSD 14.3-RELEASE-p2, nginx 1.28.0, PHP 8.3.23, WordPress 6.8.2

エラー内容

2025/09/03 07:20:27 [error] 18603#100288: *143368 FastCGI sent in stderr: "PHP message: PHP Fatal error:  Allowed memory size of 268435456 bytes exhausted (tried to allocate 4295229440 bytes) in /home/httpd/hoge/wordpress/wp-includes/theme.php on line 325" while reading response header from upstream, client: 1.2.3.4, server: kaisei-hp.co.jp, request: "GET / HTTP/2.0", upstream: "fastcgi://unix:/var/run/php-fpm.sock:", host: "hoge.jp"

エラーの意味

PHPがスクリプトを実行するために許可されたメモリ上限(memory_limit)を使い果たしてしまった。

  • 許可されたメモリ (Allowed memory size): 268,435,456バイト = 256MB
  • 確保しようとしたメモリ (tried to allocate): 4,295,229,440バイト = 約4.29GB


wp-includes/theme.phpの325行目。

return apply_filters( 'template', get_option( 'template' ) );


取り急ぎphp-fpmを再起動。
# service php_fpm restart

これでサイトは表示されるようになった。



1. エラー周辺のシステムリソース情報

このサーバーはCollectdをインストールしてあるので、エラー発生周辺時間のリソース情報を取得するシェルスクリプトをGemini先生に作ってもらった。
Gist: https://gist.github.com/DaikiSuganuma/fd49b7f88b9f5d97675d9a822104a0a1

実行してGemini先生に報告。
# cd /home/software/server-utility/
# vim get_collectd_data.sh
# chmod +x get_collectd_data.sh
# ./get_collectd_data.sh

分析結果。

  1. 問題はリソース不足ではない: サーバーの物理メモリは潤沢であり、継続的なメモリリークやシステム全体の負荷増大が原因ではない。
  2. 問題のトリガーは単発的なリクエスト: 特定のリクエスト(おそらくWordPressのトップページへのアクセス)が引き金となり、単一プロセス内で異常なメモリ要求が発生した。
  3. 再起動が問題を解消した: PHP-FPMサービス(またはワーカープロセス)が再起動したことで、問題のワーカーが強制終了され、正常な状態に戻った。


2. PHPの設定変更

今後原因を特定するためにPHPエラーログ設定をする。
この設定はWordPressのWP_DEBUG=falseでも記録されるはず。
# less /usr/local/etc/php.ini

log_errors = On
error_log = /var/log/php_errors.log

ファイルを作る。
# touch /var/log/php_errors.log
# chown www:www /var/log/php_errors.log

PHP-FPMのスローログ(Slow Log)を有効にする。
# less /usr/local/etc/php-fpm.d/www.conf

slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 10s

テストして再読み込み。
# php-fpm -t
# service php_fpm reload

Gemini先生がお勧めしてきたOPcacheのvalidate_timestamps(ファイル更新チェック)をオンにする。
# less /usr/local/etc/php/ext-10-opcache.ini

; ファイルのタイムスタンプチェックを無効化。本番環境でのパフォーマンスを最大化。
; ※注意: テーマやプラグインの更新後は、必ずphp-fpmのリロードが必要です。
; 予期せぬ動作を引き起こす可能性があるため有効にする。
opcache.validate_timestamps=1

テストして再読み込み。
# php-fpm -t
# service php_fpm reload
# php -i | grep opcache

php_errors.logの動作確認。
エラーを起こすコマンド。
# php -r '$a = 1/0;'

エラーログ確認。
# less /var/log/php_errors.log


3. Nginxのアクセスログ変更

NginxのアクセスログにPHPの処理時間を記録する。
# cd /usr/local/etc/nginx/conf.d/include/
# less log_format.inc

# --- 元のログフォーマット(比較用) ---
# log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
#                   '$status $body_bytes_sent "$http_referer" '
#                   '"$http_user_agent" "$http_x_forwarded_for"';
#
# --- [main_v2]アクセスログフォーマット ---
# ドメイン名($host)と処理時間($request_time)を追加し、より詳細な分析を可能にする
#
# [main_v2]の出力例
# 192.168.1.10 [example.com] - - [15/May/2024:14:30:00 +0900] "GET /about/ HTTP/1.1" 200 5120 "https://www.google.com/" "Mozilla/5.0..." rt=0.035 urt=0.035
#
log_format main_v2
    # $remote_addr     : [fail2banのために先頭に] リクエスト元のクライアントIPアドレス。 (例: '192.168.1.10')
    '$remote_addr '

    # $host            : リクエストヘッダのHostフィールド。アクセスされたドメイン名が入る。 (例: 'example.com')
    #                    もし$server_nameを使いたい場合は、こちらに置き換えても良い。
    '[$host] '

    # $remote_user     : Basic認証で認証されたユーザー名。認証がなければ '-' となる。 (例: '-')
    '- $remote_user '

    # [$time_local]    : サーバーのローカルタイム。 (例: '[15/May/2024:14:30:00 +0900]')
    '[$time_local] '

    # "$request"       : リクエストのメソッド、URI、プロトコルバージョン。 (例: '"GET /about/ HTTP/1.1"')
    '"$request" '

    # $status          : レスポンスのHTTPステータスコード。 (例: '200')
    '$status '

    # $body_bytes_sent : レスポンスボディのバイト数(コンテンツのサイズ)。 (例: '5120')
    '$body_bytes_sent '

    # "$http_referer"  : 参照元(どのページから来たか)。直接アクセスなら '-' となる。 (例: '"https://www.google.com/"')
    '"$http_referer" '

    # "$http_user_agent" : クライアントのブラウザやツールの情報。 (例: '"Mozilla/5.0 (Windows NT 10.0; ...)"')
    '"$http_user_agent" '

    # $request_time    : リクエスト全体(Nginxがリクエストを受け取ってからレスポンスを返すまで)の処理時間(秒)。
    #                    ネットワークの遅延なども含むため、純粋なPHP処理時間より少し長くなる。 (例: 'rt=0.035')
    'rt=$request_time '

    # $upstream_response_time: バックエンド(PHP-FPM)の処理時間(秒)。
    #                         今回の問題調査で最も重要な指標。 (例: 'urt=0.035')
    'urt=$upstream_response_time';

# 新しいフォーマット(main_v2)を使用するように設定する
access_log  /var/log/nginx/access.log  main_v2;

テストして再読み込み。
# nginx -t
# service nginx reload

アクセスログの確認。
# tail -f /var/log/nginx/access.log

fail2banを変更する必要はなし。


4. NginxのVTSとPHP OPcacheの情報

NginxのVTSの情報をJSONで取得するコマンド。Basic認証付き。
# curl --user hoge01:pass -s https://tools.hoge.jp/vts_status//format/json

PHP OPcacheの情報をJSONで取得するコマンド。
# php -r 'if (function_exists("opcache_get_status")) { echo json_encode(opcache_get_status(false), JSON_PRETTY_PRINT); }'

この情報をそのままGemini先生に報告すれば解析をしてくれる。


5. Xdebugをインストール

/var/log/php_errors.logにスタックトレースを表示したくてxdebugをインストール。

ただし本番環境でXdebugを有効にすることは推奨されない。
使わない場合は「xdebug.mode=off」に設定する。
参考: Xdebug: Documentation » All settings

pkg経由でインストール。
# pkg search xdebug
# pkg install php83-pecl-xdebug

確認。
# php --version

PHP 8.3.23 (cli) (built: Jul 17 2025 01:12:08) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.23, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.23, Copyright (c), by Zend Technologies
    with Xdebug v3.3.2, Copyright (c) 2002-2024, by Derick Rethans

iniファイルの場所確認。
# php --ini

Gemini先生に日本語コメント付きの設定ファイルを作ってもらった。
# less /usr/local/etc/php/ext-20-xdebug.ini

; ==============================================================================
; Xdebug 3 Configuration for Production Error Analysis (Corrected for v3.3.2)
; 本番環境でのエラー原因特定のために一時的に使用する設定 (Xdebug 3.3.2対応版)
; ==============================================================================

; Xdebug拡張モジュールを有効にする (これは必須です)
zend_extension=xdebug.so

; ------------------------------------------------------------------------------
; モード設定 (Mode Configuration)
; ------------------------------------------------------------------------------

; [設定] Xdebugの機能を指定します。'develop'はエラー時のスタックトレース表示を有効にします。
;        'off'にするとXdebugは完全に無効化され、パフォーマンスへの影響がなくなります。
;        原因特定後は 'off' に戻すことを強く推奨します。
xdebug.mode = develop

; デフォルト値: xdebug.mode = develop


; ------------------------------------------------------------------------------
; エラー時の動作 (Error Behavior)
; ------------------------------------------------------------------------------

; [設定] エラーや例外が発生した時にだけXdebugの機能をアクティベートします。
;        これを'yes'にすることが、本番環境でのパフォーマンス影響を最小化する鍵です。
;        正常なリクエストではXdebugはほぼ待機状態になります。
xdebug.start_upon_error = yes

; デフォルト値: xdebug.start_upon_error = default


; ------------------------------------------------------------------------------
; 変数表示の詳細設定 (Variable Display Details)
; これらの設定は、スタックトレース内の引数表示にも影響します。
; ------------------------------------------------------------------------------

; [設定] 配列の要素やオブジェクトのプロパティを最大何個まで表示するか。
;        -1は無制限。デフォルトでも十分ですが、多すぎるとログが冗長になります。
; xdebug.var_display_max_children = 128
; デフォルト値: xdebug.var_display_max_children = 128

; [設定] 文字列の最大表示長。これを超えると省略されます。
;        -1は無制限。
; xdebug.var_display_max_data = 512
; デフォルト値: xdebug.var_display_max_data = 512

; [設定] 配列やオブジェクトのネスト(階層)を最大何階層まで表示するか。
;        メモリ枯渇エラーのような深い呼び出しで詳細を確認するため、少し増やしておくと有効です。
xdebug.var_display_max_depth = 5

; デフォルト値: xdebug.var_display_max_depth = 3


; ------------------------------------------------------------------------------
; (オプション) Xdebug自体のログ設定
; ------------------------------------------------------------------------------
; Xdebugが期待通りに動作しない場合に、その原因を調査するためのログです。
; 通常はコメントアウトしたままで問題ありません。

; [オプション] Xdebug自体のログを出力するパス。
; xdebug.log = /var/log/xdebug.log
; デフォルト値: xdebug.log = "" (無効)

; [オプション] Xdebugのログレベル。0(crit)から10(trace)まで。7はInfoレベル。
; xdebug.log_level = 7
; デフォルト値: xdebug.log_level = 7

phpinfoで確認。
# php -i | grep xdebug

php-fpmを再読み込み。
# php-fpm -t
# service php_fpm reload

ブラウザでphpinfo()を表示して確認。



▼ 関連記事