Короткий ответ
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».
curl→HTTP/1.1 500, тело пустое или HTML Apache.- Access log: 500,
request_timeмалый (fatal сразу) или большой (fatal после работы).
| Признак | Не 500 приложения |
|---|---|
| 502, connect refused | FPM не запущен |
| 504 | таймаут |
| 200 + warning в HTML | display_errors уже On — уберите |
| Случайный 500 + неизвестные PHP в uploads | вредонос |
Возможные причины
- PHP Fatal: несовместимый плагин после обновления 8.2→8.3, отсутствие класса.
memory_limit128M, выгрузка каталога съедает память.- Права на
wp-config.php/ автозагрузчик — реже 500, чаще warning; ноrequireотсутствующего файла — Fatal. .htaccessс директивами nginx-синтаксиса на Apache (и наоборот не читается).open_basedirрежет путь после переноса.- Исключение Laravel/Symfony без записи в
storage/logsиз‑за прав на log. - Модуль Apache
php+ невернаяphp.ini. - Битая сериализация в
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_errorsCLI 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-content4. Воспроизвести один запрос
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 = Offdisplay_startup_errors = Offlog_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 = 256Msudo 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 journaljournalctl -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.