Короткий ответ
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 хватает, сайт просто медленный |
Возможные причины
- Тяжёлый PHP-запрос (отчёт, импорт,
WP_Queryбез лимита) дольше, чемfastcgi_read_timeout. - MySQL ждёт lock:
SHOW PROCESSLIST,Waiting for table metadata lock. proxy_read_timeout60 с, а Node делает внешний вызов без своего deadline.- PHP-FPM
request_terminate_timeoutменьше или равен nginx — процесс убивают, nginx видит обрыв/таймаут. - DNS в приложении: резолв внешнего API из PHP зависает.
- Диск 100% util, PHP ждёт I/O.
- Слишком короткий timeout после «оптимизации» (10 с при легитимных 15 с экспортах).
- 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.