エラーの概要
Nginx のエラーログには次のような内容が記録されます:
2024/01/15 10:23:41 [error] 12345#12345: *1 upstream sent invalid header while reading response header from upstream, client: 192.168.1.10, server: example.com, request: "GET /index.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.1-fpm.sock", host: "example.com"
ブラウザには 502 エラーが返されます。これは単純な 502(PHP-FPM が起動していない)や 504(タイムアウト)とは異なります。この場合、Nginx は PHP-FPM への接続自体は成功しています — しかし返ってきた内容が不正なものでした。PHP-FPM が Nginx では有効な FastCGI レスポンスヘッダーとして解析できないものを返したのです。
原因
いくつかの原因が考えられます。よくある原因は以下のとおりです:
- PHP スクリプトがヘッダーの前に生のテキストや HTML を出力している —
header()呼び出しより前に不要な警告、通知、または BOM がある場合 fastcgi_passの設定が誤っている — FastCGI ソケットやポートではなく HTTP プロキシ URL を指定している- Nginx の設定と PHP-FPM が実際にリッスンしているソケットパスが一致していない
- PHP-FPM プールの設定ミスにより、レスポンス送信中にワーカーがクラッシュしている
fastcgi_params/fastcgi_indexディレクティブが欠落しているか誤っている
ステップ 1: まず PHP-FPM エラーログを確認する
まだ Nginx には手を加えないでください。PHP-FPM の実際の動作から確認します:
# Ubuntu/Debian
tail -n 50 /var/log/php8.1-fpm.log
# またはプール別(www はデフォルトのプール名)
tail -n 50 /var/log/php/8.1/fpm/www-error.log
PHP の致命的エラー、ヘッダー前出力に関する警告、またはセグフォルトがここに記録されている場合、問題は Nginx ではなく PHP 側にあります。まずそちらを修正してください。
プール固有のエラーログも確認してください。/etc/php/8.1/fpm/pool.d/www.conf に次の設定があることを確認します:
catch_workers_output = yes
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php8.1-fpm-www.log
ステップ 2: fastcgi_pass の設定を確認する
ほとんどの場合、これが原因です。Nginx のサーバーブロックを確認します:
grep -n 'fastcgi_pass' /etc/nginx/sites-enabled/your-site.conf
よくある誤った設定:
# 誤り — FastCGI ではなく HTTP プロキシを指定している
fastcgi_pass http://127.0.0.1:9000;
# 誤り — ソケットパスが存在しない
fastcgi_pass unix:/var/run/php-fpm.sock;
# 正しい — http:// なし、host:port のみ
fastcgi_pass 127.0.0.1:9000;
# 正しい — ソケット(パスの存在を確認すること)
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
ソケットが存在し、PHP-FPM が実際にそのソケットをリッスンしていることを確認します:
# ソケットファイルを確認
ls -la /run/php/php8.1-fpm.sock
# または TCP ポートを確認
ss -tlnp | grep php
# 次のように表示されるはず: LISTEN ... 127.0.0.1:9000
PHP-FPM の設定と照合します:
grep 'listen =' /etc/php/8.1/fpm/pool.d/www.conf
# listen = /run/php/php8.1-fpm.sock
# または
# listen = 127.0.0.1:9000
fastcgi_pass の値は完全に一致している必要があります — 1 文字も違わず。
ステップ 3: 必須の FastCGI ディレクティブを確認する
最小限の構成でも動作する PHP-FPM の location ブロックは次のとおりです:
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
見落としやすいディレクティブが 2 つあります。SCRIPT_FILENAME がないと、PHP-FPM はどのファイルを実行すべきか分からず、空または不正なレスポンスを返します。include fastcgi_params がないと、必要な環境変数が一切設定されません。
そのファイルが存在することを確認してください:
ls -la /etc/nginx/fastcgi_params
cat /etc/nginx/fastcgi_params
ステップ 4: PHP のヘッダー前出力を確認する
header() 呼び出しより前の出力 — スペース 1 つ、UTF-8 BOM、または PHP の通知でさえ — PHP-FPM が先に生のコンテンツを送信する原因になります。Nginx はそれをレスポンスヘッダーとして受け取り、解析に失敗してまさにこのエラーをログに記録します。
これらを検出するために、php.ini で詳細なエラーログを有効にします:
# /etc/php/8.1/fpm/php.ini
log_errors = On
error_reporting = E_ALL
display_errors = Off # 本番環境では絶対に On にしない
error_log = /var/log/php8.1-fpm-errors.log
FPM をリロードしてエラーを再現します:
systemctl reload php8.1-fpm
curl -v http://your-domain/problematic-page.php
tail -f /var/log/php8.1-fpm-errors.log
ステップ 5: PHP-FPM プールの設定を確認する
高負荷時 — 例えば 2GB RAM のサーバーで 50 以上の PHP リクエストが同時に来た場合 — ワーカーがメモリを使い果たしてレスポンス送信中にクラッシュすることがあります。そのクラッシュがまさにこのエラーを引き起こします。プールの上限設定を確認してください:
# /etc/php/8.1/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.max_requests = 500 # N リクエスト後にワーカーをリサイクル(メモリリーク対策)
プールのヘルス状態をリアルタイムで監視しますか?ステータスページを有効にしましょう:
# プール設定でステータスページを有効化
pm.status_path = /fpm-status
# 次に Nginx 設定:
location /fpm-status {
allow 127.0.0.1;
deny all;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# クエリを実行:
curl http://127.0.0.1/fpm-status
修正の適用
設定を確認せずに Nginx をリロードしてはいけません。まず設定をテストします:
nginx -t
# nginx: 設定ファイル /etc/nginx/nginx.conf のテストに成功しました
systemctl reload nginx
PHP-FPM も同様に:
php-fpm8.1 -t
# [15-Jan-2024 10:30:00] NOTICE: 設定ファイル /etc/php/8.1/fpm/php-fpm.conf のテストに成功しました
systemctl reload php8.1-fpm
動作確認
テストリクエストを送信しながら Nginx のエラーログをリアルタイムで監視します:
tail -f /var/log/nginx/error.log &
curl -I https://your-domain/index.php
正常なレスポンスは次のようになります:
HTTP/2 200
content-type: text/html; charset=UTF-8
x-powered-by: PHP/8.1.x
エラーログから upstream sent invalid header の行が消えていれば、修正完了です。
再発防止策
- php.ini で
output_buffering = Onを設定する — PHP がヘッダーを処理する前の意図しない早期出力を吸収し、このクラスのエラーを防ぎます - 本番環境では
display_errors = Offを維持する。stdout に書き込まれた PHP 警告がまさにこの問題を引き起こします - ソケットパスは 1 か所で定義し、PHP-FPM プール設定と Nginx 設定の両方から参照する — もう一方の設定場所へのコメントを追加すると管理しやすくなります
- 設定変更をプッシュする前に CI/CD パイプラインで
nginx -t && php-fpm8.1 -tを実行する - プール設定で
pm.max_requests = 500を設定する — ワーカーは 500 リクエスト後にリサイクルされ、メモリリークしたワーカーが不正な出力を送信するのを防ぎます

