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

504: nginx установил соединение с PHP-FPM/Node/Apache и не дождался ответа за fastcgi_read_timeout / proxy_read_timeout. Это противоположность 502, где connect сразу refused. На Ubuntu дефолт nginx для read timeout часто 60 с — страница «крутится минуту» и падает. Хост site.example, IP 10.0.20.40, корень /var/www/site.example.

Сначала измерьте, жив ли бэкенд и чем занят: SQL, lock, внешний HTTP, диск. Увеличение timeout без профиля — маскировка. Если TTFB 2 с при timeout 60 с — проблема не в nginx.

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

  • Браузер ждёт ~30–60–120 с, затем 504.
  • Access log: $request_time ≈ значению timeout, статус 504.
  • Error log: upstream timed out (110: Connection timed out) while reading response header from upstream.
НаблюдениеВывод
504 за 1 сconnect timeout / неверный маршрут, ближе к сети
504 ровно на 60.00 супёрлись в дефолт nginx
502 мгновенносокет мёртв
200 за 45 сtimeout хватает, сайт просто медленный

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

  1. Тяжёлый PHP-запрос (отчёт, импорт, WP_Query без лимита) дольше, чем fastcgi_read_timeout.
  2. MySQL ждёт lock: SHOW PROCESSLIST, Waiting for table metadata lock.
  3. proxy_read_timeout 60 с, а Node делает внешний вызов без своего deadline.
  4. PHP-FPM request_terminate_timeout меньше или равен nginx — процесс убивают, nginx видит обрыв/таймаут.
  5. DNS в приложении: резолв внешнего API из PHP зависает.
  6. Диск 100% util, PHP ждёт I/O.
  7. Слишком короткий timeout после «оптимизации» (10 с при легитимных 15 с экспортах).
  8. NPM/Docker: таймаут прокси по умолчанию, бэкенд на хосте жив.

Диагностика

1. Зафиксировать факт 504 и время

sudo tail -n 30 /var/log/nginx/error.log
sudo awk '$9==504 {print $4,$10,$NF}' /var/log/nginx/access.log | tail
curl -o /dev/null -s -w 'http:%{http_code} time:%{time_total}\n' \
  --max-time 90 --resolve site.example:443:10.0.20.40 https://site.example/slow.php

Подставьте реальный медленный URL, не выдумывайте slow.php в проде — используйте путь из access.log.

2. Таймауты nginx

sudo nginx -T 2>/dev/null | grep -E 'fastcgi_read_timeout|proxy_read_timeout|fastcgi_send_timeout|proxy_connect_timeout'

Дефолт без директивы: 60s для read. proxy_connect_timeout влияет на установку TCP, не на долгий PHP.

3. PHP-FPM slowlog и terminate

sudo grep -E 'request_terminate_timeout|slowlog|request_slowlog_timeout' /etc/php/8.3/fpm/pool.d/www.conf
sudo tail -n 80 /var/log/php8.3-fpm.log
sudo tail -n 80 /var/log/php8.3-fpm-slow.log 2>/dev/null

Включите slowlog временно с request_slowlog_timeout = 5s, reload FPM. Это диагностика, не постоянный debug в проде без ротации.

4. SQL

С сервера БД (плейсхолдер хоста тот же стенд):

SHOW FULL PROCESSLIST;

Ищите Time больше десятков секунд, Sleep с открытой транзакцией, неотключенный lock. Медленный сайт целиком — цепочка TTFB.

5. Кто занят на 10.0.20.40

top -b -n 1 | head -n 20
ps -eo pid,ppid,cmd,%cpu,etime | grep -E 'php-fpm|mysqld|node'

Много php-fpm: pool www в D-state — диск/NFS, не timeout nginx.

Решение

Не поднимайте timeout до 600 с «на всякий случай»: займутся воркеры FPM, очередь, 502 из‑за pm.

Сценарий A. Легитимный длинный запрос (экспорт)

Вынесите в CLI/очередь. Если нельзя сразу — точечно для location:

location /wp-admin/admin.php {
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_read_timeout 180s;
}

Согласуйте с FPM:

request_terminate_timeout = 180s

И с PHP max_execution_time в /etc/php/8.3/fpm/php.ini. Все три числа должны быть согласованы: nginx ≥ PHP ≥ ожидание бизнеса. Иначе убийца будет «случайным».

Сценарий B. Короткий timeout при нормальном бэкенде

Верните разумные 60–120 с, найдите регресс в приложении. Не ставьте fastcgi_read_timeout 10s на весь location ~ \.php$.

Сценарий C. Зависший SQL

Снимите проблемный запрос по правилам DBA стенда, добавьте индекс, уберите SELECT * без LIMIT в админке. Timeout nginx тут ни при чём.

Сценарий D. Node за proxy_pass

proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

В самом Node поставьте timeout на исходящие HTTP, иначе 60 с будет каждый раз.

Сценарий E. Apache ProxyTimeout

ProxyTimeout 60

Синхронизируйте с бэкендом. TimeOut самого Apache — другой параметр (общий I/O).

Сценарий F. PHP 8.2/8.3 max_execution_time и max_input_time

В /etc/php/8.3/fpm/php.ini (не cli/php.ini) max_execution_time = 30 при nginx timeout 60 с даёт обрыв на 30-й секунде. В логе FPM будет execution timed out, nginx — upstream prematurely closed или 504. Выровняйте: бизнес-лимит ≤ PHP ≤ FPM terminate ≤ nginx read. Для CLI-импортов используйте php CLI с отдельным php.ini, не раздувайте FPM под ночные отчёты.

На Apache ProxyTimeout и TimeOut в apache2.conf — разные рычаги. Если фронт nginx, а Apache — бэкенд на 8080, таймаут нужно смотреть на обоих хопах: короткий nginx 60 с при Apache 300 с всё равно даст 504 на 60-й.

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

Повторите тот же URL из access.log:

curl -o /dev/null -s -w 'http:%{http_code} ttfb:%{time_starttransfer} total:%{time_total}\n' \
  --max-time 90 --resolve site.example:443:10.0.20.40 https://site.example/

Ожидание: не 504. Если 200 за 3 с — nginx timeout не был узким местом после фикса SQL/PHP. Если 200 за 70 с при timeout 180 — проблема производительности осталась, 504 только спрятали.

Проверьте, что обычные страницы не деградировали (php-fpm active processes).

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

  • 504 только через CDN: таймаут edge (часто 60–100 с) меньше origin. Смотрите настройки CDN, не только nginx.
  • 504 на POST больших файлов: client_body_timeout / client_max_body_size.
  • 504 при живом FPM и пустом slowlog: зависание в DNS connect() из PHP, strace -p <pid> точечно на воркере.
  • NPM: UI Timeout, не системный nginx.

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

  • Алерты на долю 504 и p95 request_time.
  • Slowlog FPM с ротацией.
  • Бюджет времени на синхронный HTTP в коде.
  • Экспорт/отчёты — в очередь, не в request/response.

FAQ

Почему 502 не 504, если FPM убит по terminate?

Иногда nginx пишет upstream prematurely closed connection и отдаёт 502. Смотрите лог, не только цифру в браузере.

Можно ли fastcgi_read_timeout 0?

В nginx 0 часто значит «дефолт», не «бесконечно». Не опирайтесь на это.

Apache за nginx тоже 504?

Да, если nginx не дождался Apache. Тогда профилируйте Apache/PHP так же.

Нужно ли поднимать keepalive_timeout?

Нет, он про простой keep-alive клиента, не про чтение апстрима.

WordPress «крутится» на обновлении плагина

Долгий админ-запрос. Временно поднимите timeout только /wp-admin/, лучше обновляйте WP-CLI по SSH.