Короткий ответ

500 — приложение или интерпретатор упали после того, как веб-сервер уже передал запрос. Nginx при этом часто пишет AH01071/upstream sent too big header реже, чем PHP Fatal в логе FPM. На site.example (10.0.20.40, /var/www/site.example) откройте php8.3-fpm.log / site.example-error.log, воспроизведите один URL, читайте последний Fatal. Не ставьте display_errors = On в проде: стек и пути утекут в HTML.

502 — мёртвый FPM (502). 500 — FPM ответил кодом 500 или пустым FastCGI abort после fatal.

Симптомы и как отличить

  • Белый экран / «Internal Server Error».
  • curlHTTP/1.1 500, тело пустое или HTML Apache.
  • Access log: 500, request_time малый (fatal сразу) или большой (fatal после работы).
ПризнакНе 500 приложения
502, connect refusedFPM не запущен
504таймаут
200 + warning в HTMLdisplay_errors уже On — уберите
Случайный 500 + неизвестные PHP в uploadsвредонос

Возможные причины

  1. PHP Fatal: несовместимый плагин после обновления 8.2→8.3, отсутствие класса.
  2. memory_limit 128M, выгрузка каталога съедает память.
  3. Права на wp-config.php / автозагрузчик — реже 500, чаще warning; но require отсутствующего файла — Fatal.
  4. .htaccess с директивами nginx-синтаксиса на Apache (и наоборот не читается).
  5. open_basedir режет путь после переноса.
  6. Исключение Laravel/Symfony без записи в storage/logs из‑за прав на log.
  7. Модуль Apache php + неверная php.ini.
  8. Битая сериализация в wp_options после голого sed.

Диагностика

1. Какой слой отдал 500

curl -sI --resolve site.example:443:10.0.20.40 https://site.example/ | head
curl -s --resolve site.example:443:10.0.20.40 https://site.example/ | tail -n 20
sudo tail -n 50 /var/log/nginx/error.log
sudo tail -n 50 /var/log/apache2/site.example-error.log 2>/dev/null
sudo tail -n 80 /var/log/php8.3-fpm.log
sudo tail -n 80 /var/log/php8.2-fpm.log 2>/dev/null

Ищите PHP Fatal error, PHP Parse error, Uncaught.

2. Эффективный php.ini FPM, не CLI

sudo php-fpm8.3 -i 2>/dev/null | grep -E 'display_errors|error_log|memory_limit|disable_functions'
# или
sudo grep -E 'display_errors|error_log|memory_limit' /etc/php/8.3/fpm/php.ini /etc/php/8.3/fpm/conf.d/*
php -i | grep display_errors

CLI php — другой SAPI. Не смотрите только его.

3. WordPress / Laravel логи

sudo tail -n 80 /var/www/site.example/wp-content/debug.log 2>/dev/null
sudo tail -n 80 /var/www/site.example/storage/logs/laravel.log 2>/dev/null
ls -ld /var/www/site.example/storage /var/www/site.example/wp-content

4. Воспроизвести один запрос

curl -s -o /tmp/body -w '%{http_code}\n' --resolve site.example:443:10.0.20.40 https://site.example/wp-login.php

Сразу tail логов. Не флудите сканером — засорите журнал.

5. Не включать вывод ошибок в браузер

Проверьте, что в проде:

  • display_errors = Off
  • display_startup_errors = Off
  • log_errors = On

В wp-config.php допустимо:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
@ini_set('display_errors', 0);

Решение

Сценарий A. Fatal в плагине/теме

По логу отключите конкретный файл. WP-CLI:

sudo -u www-data wp --path=/var/www/site.example plugin deactivate offending-plugin

Без WP-CLI — переименуйте каталог плагина в wp-content/plugins/offending-plugin.off. Не rm -rf плагины без копии.

sudo tar -C /var/www/site.example/wp-content -czf /root/plugins-$(date +%F).tar.gz plugins

Сценарий B. memory_limit

В /etc/php/8.3/fpm/php.ini или пуле:

memory_limit = 256M
sudo systemctl reload php8.3-fpm

Если 1G не хватает на витрине — это утечка/выгрузка всего каталога, не «надо ещё». Профилируйте, см. медленный сайт.

Сценарий C. Parse error после правки

Откатите файл из git/бэкапа. php -l /var/www/site.example/wp-config.php ловит parse, но не runtime.

Сценарий D. Laravel не пишет лог

sudo chown -R www-data:www-data /var/www/site.example/storage /var/www/site.example/bootstrap/cache
sudo find /var/www/site.example/storage -type d -exec chmod 775 {} \;

Не 777 на весь проект.

Сценарий E. .htaccess ломает Apache

Временный rename .htaccess, config не нужен — достаточно 500 из Invalid command. Верните построчно. На nginx .htaccess не действует; 500 будет из PHP.

Сценарий F. open_basedir и systemd PrivateTmp

После переноса в /var/www/site.example PHP всё ещё ограничен open_basedir=/var/www/html в пуле — require вендора даёт Fatal, 500, в логе open_basedir restriction. Поправьте пул, reload FPM. PrivateTmp=true у php8.3-fpm.service не виноват в 500 HTML, но ломает самописные cron, которые ждут файлы в /tmp процесса FPM.

Не ставьте display_errors=On в php.ini «на пять минут» через Ansible без when: env != prod. Откат забывают. Для одного запроса достаточно tail -F лога.

Как проверить, что проблема устранена

curl -sI --resolve site.example:443:10.0.20.40 https://site.example/ | head
sudo grep -i 'display_errors' /etc/php/8.3/fpm/php.ini
sudo -u www-data wp --path=/var/www/site.example eval 'echo ini_get("display_errors");' 2>/dev/null

Страница 200/302. display_errors должен быть Off/0. Повторите URL, который давал 500. Убедитесь, что в HTML нет Fatal error и пути /var/www.

Если не помогло

  • 500 только на POST больших тел: post_max_size / client_max_body_size.
  • 500 после деплоя opcache: opcache_reset через reload FPM.
  • Пустой лог: другой пул, другой error_log в php.ini, systemd journal journalctl -u php8.3-fpm.
  • Случайные 500 — OOM killer, смотрите dmesg | grep php-fpm, это уже FPM.

Профилактика

  • log_errors On, display_errors Off в образе FPM.
  • Staging с копией данных для включения debug display.
  • Алерты на долю 500 в access.log.
  • Обновление PHP 8.2→8.3 сначала на стенде.

FAQ

Можно ли ini_set('display_errors', 1) «на минуту» в index.php?

На проде — нет. Запрос поисковика или клиента утечёт. Только лог.

Белый экран без 500 в access?

Иногда PHP отдаёт 200 и пустое тело. Всё равно читайте Fatal. Настройте fastcgi_intercept_errors осознанно.

Apache пишет 500, nginx 200 на статике

Только PHP location. Сверьте, что .php уходит в FPM.

Нужен ли error_reporting(E_ALL) в проде?

Логировать E_ALL можно, отображать — нет. На загруженных сайтах сузьте, чтобы не забить диск.

500 сразу после certbot

Редко связан с TLS. Проверьте, не перезаписал ли плагин vhost root/PHP handler. TLS-ошибки браузера — не HTTP 500.