Решение проблем с Nginx 504 Gateway Timeout и тайм-аутами клиента

Освойте тайм-ауты Nginx, включая пугающий 504 Gateway Timeout, научившись настраивать критические прокси-директивы. Это руководство подробно объясняет, как увеличить `proxy_read_timeout`, оптимизировать буферизацию и использовать журналы ошибок для диагностики сбоев связи между Nginx и вышестоящими серверами для надежной обработки соединений.

Решение проблем с Nginx 504 Gateway Timeout и тайм-аутами клиента

Ошибка Nginx 504 Gateway Timeout означает, что Nginx, выступая в роли прокси или шлюза, не получил ответ от вышестоящего сервиса вовремя. Вышестоящим сервером может быть приложение Node.js, Gunicorn, PHP-FPM, другой сервис Nginx, внутренний API или балансировщик нагрузки.

Заманчивое решение — увеличить все тайм-ауты до пяти минут и забыть. Иногда более длительный тайм-аут — правильное краткосрочное решение, особенно для отчетов, импорта или задач, доступных только администраторам. Но если обычному пользовательскому запросу требуется больше времени, чем сейчас разрешает Nginx, стоит также спросить, почему бэкенд медленный, следует ли сделать запрос асинхронным и не имеет ли другой прокси на пути более короткий тайм-аут.


Понимание ошибки 504 Gateway Timeout

Ошибка 504 Gateway Timeout возникает, когда Nginx, действуя как обратный прокси или шлюз, не получает своевременного ответа от вышестоящего сервера, которому он пересылает запросы. Простыми словами: Nginx спросил бэкенд, подождал настроенное количество времени и сдался, так как ответ не пришел.

Это отличается от 502 Bad Gateway, когда Nginx получил неверный или преждевременно закрытый ответ от вышестоящего сервера, и от 503 Service Unavailable, что часто означает, что сервис намеренно недоступен или перегружен. Различие важно, потому что 504 указывает на ожидание, тайм-ауты и задержки на стороне бэкенда.

Ключевые директивы, управляющие тайм-аутами вышестоящих серверов

При проксировании запросов Nginx использует несколько критических директив, в основном расположенных в блоках http, server или location, или конкретно в блоке upstream. Настройка этих значений является основным методом решения ошибок 504.

1. proxy_connect_timeout

Устанавливает тайм-аут для установления соединения с вышестоящим сервером. Если Nginx не может подключиться в течение этого времени, он возвращает ошибку тайм-аута.

По умолчанию: 60 секунд

proxy_connect_timeout 60s;

2. proxy_send_timeout

Устанавливает тайм-аут между двумя последовательными операциями записи на вышестоящий сервер. Это актуально при отправке большого тела запроса.

По умолчанию: 60 секунд

proxy_send_timeout 60s;

3. proxy_read_timeout (Самое распространенное исправление для 504)

Устанавливает тайм-аут ожидания ответа от вышестоящего сервера после отправки заголовков запроса. Если бэкенд-приложение слишком долго обрабатывает запрос и генерирует тело ответа, это та директива, которую нужно увеличить.

По умолчанию: 60 секунд

# Пример: Увеличение тайм-аута чтения до 120 секунд для медленного API
proxy_read_timeout 120s;

Если ваше приложение часто превышает значение по умолчанию, увеличивайте это значение осторожно и продолжайте расследование. Очень высокое значение может держать клиентские соединения открытыми, в то время как бэкенд уже нездоров.


Решение проблем с тайм-аутами на стороне клиента

Тайм-ауты на стороне клиента — это другой тип сбоя. Браузер, мобильное приложение, балансировщик нагрузки, CDN или вызывающий сервис сдаются раньше, чем Nginx завершает ответ. В этом случае пользователь может увидеть ошибку браузера или ошибку шлюза от слоя перед Nginx, в то время как Nginx может зарегистрировать закрытое соединение, а не чистый 504.

Если вы испытываете тайм-ауты клиента до того, как Nginx зарегистрирует 504, вам нужно посмотреть на соединение между клиентом и Nginx.

1. Keepalive на стороне клиента

Если клиент преждевременно закрывает соединение, Nginx может получить ошибку, или клиент может просто выйти из тайм-аута в ожидании данных.

Если клиентом является другой прокси или балансировщик нагрузки, проверьте его настройки тайм-аута по сравнению с Nginx и бэкендом. Самый короткий тайм-аут в цепочке обычно побеждает. Типичный сценарий: CDN ждет 100 секунд, балансировщик нагрузки ждет 60 секунд, Nginx ждет 180 секунд, бэкенд работает 120 секунд. Пользователи все равно терпят неудачу через 60 секунд, потому что балансировщик нагрузки сдается первым.

2. send_timeout в Nginx

Эта директива управляет тем, как долго Nginx будет ждать, пока клиент подтвердит или получит данные (время между двумя последовательными операциями записи клиенту).

По умолчанию: 60 секунд

# Установите это, если клиенты выходят из тайм-аута, пока Nginx отправляет ответ
send_timeout 120s;

Оптимизация буферизации для больших ответов

Иногда бэкенд начинает отвечать, но доставка все еще медленная, потому что ответ огромен, клиент медленный, или Nginx должен буферизировать больше, чем ожидалось. Это часто встречается при сгенерированных CSV-экспортах, загрузках медиафайлов через приложение или API, возвращающих очень большие JSON-нагрузки.

Nginx использует буферы для временного хранения данных, полученных от вышестоящего сервера, перед отправкой их клиенту. Если ответ очень большой, эти буферы могут быть превышены, что приводит к сложной обработке или воспринимаемой задержке.

Ключевые директивы буферизации

Обычно устанавливаются в блоке location или server:

Директива Назначение
proxy_buffers Устанавливает количество и размер буферов, используемых для чтения ответа от вышестоящего сервера. Формат: число размер;
proxy_buffer_size Устанавливает размер первого буфера, который используется для чтения заголовка ответа.
proxy_max_temp_file_size Если ответ превышает доступные буферы, Nginx записывает во временные файлы. Это устанавливает максимальный размер для этих временных файлов.

Пример конфигурации для больших объемов/ответов:

location /api/heavy_report {
    proxy_pass http://backend_app;

    # Увеличение тайм-аута чтения
    proxy_read_timeout 180s;

    # Настройка буферизации для потенциально больших тел ответов
    # Использовать 8 буферов, каждый до 1 МБ (1024k)
    proxy_buffers 8 1024k;
    proxy_buffer_size 256k;

    # Разрешить временные файлы до 500 МБ, если буферы переполняются
    proxy_max_temp_file_size 500m;
}

Если ответ вашего бэкенда действительно огромен, рассмотрите возможность обслуживания сгенерированного файла из объектного хранилища или статического хранилища вместо того, чтобы держать запрос открытым через приложение. Для экспорта распространенным шаблоном является: поставить задачу в очередь, сгенерировать файл, затем позволить пользователю скачать его по статическому URL, когда он будет готов.


Шаги по устранению неполадок и анализ журналов

Решение проблем с тайм-аутами требует точного определения места задержки: Клиент -> Nginx или Nginx -> Бэкенд.

Шаг 1: Проверьте журналы ошибок Nginx

Журнал ошибок Nginx является вашим окончательным источником для определения того, превысил ли Nginx время ожидания ответа от бэкенда.

Ищите записи, содержащие фразы вроде:

  • upstream timed out (110: Connection timed out)
  • upstream prematurely closed connection while reading response header from upstream

Если вы видите их, проблема связана с proxy_read_timeout или временем обработки бэкенда.

Также ищите client prematurely closed connection. Обычно это означает, что клиент или прокси перед Nginx сдались первыми. В этом случае простое увеличение proxy_read_timeout не поможет пользователю.

Шаг 2: Проверьте журналы бэкенд-приложения

Если Nginx превышает тайм-аут (журналы указывают на 504), немедленно проверьте журналы вышестоящего сервиса (например, журналы PHP-FPM, журналы Gunicorn, журналы сервера приложений Java). Вам нужно подтвердить, достиг ли запрос бэкенда и сколько времени заняла его обработка.

  • Если журналы бэкенда показывают, что запрос занял больше времени, чем ваш настроенный proxy_read_timeout, увеличьте тайм-аут Nginx.
  • Если журналы бэкенда показывают, что запрос завершился быстро, проблема может быть в задержке сети между Nginx и бэкендом или в неправильно настроенном тайм-ауте клиента, обращенного к Nginx.

Шаг 3: Используйте заголовок X-Upstream-Response-Time (Опционально)

Для детальной диагностики вы можете записывать точное время, которое вышестоящий сервер затратил на ответ, используя переменную $upstream_response_time в формате вашего журнала доступа. Это помогает подтвердить фактическую производительность бэкенда.

В вашем nginx.conf:

log_format proxy_detailed '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" $request_time $upstream_response_time';

access_log /var/log/nginx/access.log proxy_detailed;

Анализируя $upstream_response_time, вы можете увидеть точную продолжительность ожидания Nginx, независимо от его собственных настроек тайм-аута.

Для быстрого одноразового теста вызовите вышестоящий сервер напрямую с хоста Nginx:

time curl -sS -o /dev/null -w 'status=%{http_code} total=%{time_total}\n' http://127.0.0.1:3000/slow-route

Если прямой вызов вышестоящего сервера уже медленный, Nginx только сообщает о проблеме. Если прямой вызов быстрый, но проксированный запрос превышает тайм-аут, проверьте конфигурацию прокси, разрешение DNS, сеть контейнеров, TLS между внутренними сервисами или другой переход между Nginx и приложением.


Примените наименьшее полезное изменение

Разумное производственное исправление часто выглядит так:

location /api/reports/ {
    proxy_pass http://backend_app;
    proxy_connect_timeout 10s;
    proxy_send_timeout 60s;
    proxy_read_timeout 180s;
}

Это увеличивает тайм-аут чтения только для медленной конечной точки отчетов. Это не заставляет каждый запрос на сайте ждать три минуты. Для маршрута входа, маршрута оформления заказа, проверки работоспособности или публичной конечной точки API длительный тайм-аут может сделать сбои более болезненными, потому что клиенты дольше ждут запроса, который вряд ли восстановится.

Для PHP-FPM эквивалент может включать директивы FastCGI:

location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    fastcgi_read_timeout 120s;
    include fastcgi_params;
}

Помните, что PHP, Python, Node.js, серверы приложений, очереди, базы данных, CDN и балансировщики нагрузки могут иметь свои собственные настройки тайм-аута. Nginx не может заставить бэкенд продолжать работать после того, как собственный тайм-аут рабочего процесса бэкенда убивает запрос.

После внесения любых изменений в конфигурацию (например, увеличения тайм-аутов или настройки размеров буферов) всегда проверяйте синтаксис конфигурации и перезагружайте Nginx:

sudo nginx -t
sudo systemctl reload nginx

Затем следите за журналами как Nginx, так и вышестоящего сервера, повторяя тот же запрос:

sudo tail -f /var/log/nginx/error.log

Лучшее исправление тайм-аута оставляет вас с четкой причиной изменения: этот маршрут законно занимает до двух минут, этот вышестоящий сервер теперь имеет соответствующие лимиты, и медленные запросы видны в журналах или метриках. Все, что меньше, является временным патчем, и временные патчи должны быть помечены как таковые в вашем обзоре конфигурации или заметках об инциденте.