Таймауты, повторы и устойчивость прокси

Рейтинг: 74.1% · 11 голосов
Самый подробный курс по Nginx - от первого конфига до production API-gateway. Архитектура и event loop, контексты и location, виртуальные хосты, статика, reverse proxy и upstream-балансировка, кэширование, HTTPS и TLS (post-quantum), HTTP/2 и HTTP/3 QUIC, безопасность и rate limiting, realip, JSON-логи и сквозная трассировка, метрики, тюнинг и траблшутинг. Плюс OpenResty (Lua, фазы, JWT, динамика) и Angie (нативные Prometheus, ACME, dynamic upstream). На основе эталонного gateway и лучших практик. Для новичков и SRE/DevOps. Актуально на 2026 (nginx 1.30/1.31, Angie, ingress-nginx EOL -> Gateway API).
Ответить
Аватара пользователя
Igor_NgINX
Сообщения: 44
Зарегистрирован: 11 май 2026, 05:31

Таймауты, повторы и устойчивость прокси

Сообщение Igor_NgINX »

Оглавление курса (44)
  1. Что такое Nginx и почему он захватил веб
  2. Установка Nginx, Angie и OpenResty: пакеты, версии, Docker
  3. Первый конфиг с нуля: минимальный сервер за 5 минут
  4. Архитектура процессов: master, worker и event loop
  5. Анатомия конфигурации: контексты, наследование и модульность
  6. Виртуальные хосты: server и server_name
  7. Директива location: матчинг и приоритеты по шагам
  8. Раздача статики: root, alias, try_files, sendfile
  9. Переменные, map и блок if
  10. rewrite, return и канонизация URL
  11. Работа с заголовками: add_header и proxy_set_header
  12. Модель доверия прокси-заголовков: realip и защита от спуфинга
  13. Сжатие: gzip, brotli и zstd
  14. Reverse proxy: proxy_pass и проброс запроса
  15. upstream и балансировка нагрузки
  16. Таймауты, повторы и устойчивость прокси (вы здесь)
  17. FastCGI и PHP: nginx + PHP-FPM (и uwsgi/scgi)
  18. WebSocket, gRPC и стриминг через Nginx
  19. Кэширование ответов: proxy_cache и микрокэш
  20. HTTPS и TLS: сертификаты, протоколы, шифры (2026)
  21. Let's Encrypt и ACME: certbot, acme.sh, нативный ACME Angie
  22. HTTP/2 и HTTP/3 (QUIC) в 2026
  23. Безопасность: заголовки, скрытие версии, ограничения
  24. Rate limiting и защита от перегрузки
  25. Контроль доступа: allow/deny, auth_basic, auth_request и mTLS
  26. Логирование: access_log, форматы и структурированный JSON
  27. Сквозная трассировка запросов и OpenTelemetry
  28. Метрики и мониторинг Nginx
  29. Производительность и тюнинг под нагрузку
  30. Траблшутинг: 502, 504, 403, 404 и debug-лог
  31. OpenResty: Nginx как платформа на LuaJIT
  32. Фазы обработки запроса и Lua-хуки
  33. Lua API: ngx.*, shared dict, cosocket и lua-resty-core
  34. Практика OpenResty: авторизация, JWT, кэш, Redis
  35. Angie: современный форк Nginx 2026
  36. Динамическая конфигурация и service discovery
  37. Nginx как API-gateway и Kubernetes Gateway API
  38. Динамические модули, njs и WAF
  39. Stream-модуль: проксирование TCP и UDP
  40. Деплой, перезагрузка и конфиг как код
  41. Сценарий: статика, SPA и кэширование за CDN
  42. Сценарий: микросервисный gateway по образцу ngx-trace-gateway
  43. Капстоун: собираем production-gateway с нуля
  44. Чек-лист безопасности, CVE 2026 и аудит конфигурации
Боль: один тормозящий бэкенд кладёт весь шлюз

Сценарий из жизни. У тебя апстрим из четырёх приложений, фронт - nginx. В пятницу вечером одна нода ловит сборщик мусора и начинает отвечать по 40 секунд вместо 80 миллисекунд. Через минуту мониторинг краснеет весь: время ответа выросло у всех урлов, воркеры заняты, новые соединения встают в очередь. Виноват один бэкенд, а страдает весь сервис. Почему? Потому что nginx по умолчанию готов ждать ответа очень долго, не размыкает соединение и держит слот занятым.

Эта глава - про то, как сделать прокси устойчивым к нестабильному бэкенду. Мы разберём три кита надёжности: таймауты (когда сдаваться), повторы и failover (куда уходить при сбое) и защиту самого nginx от медленных клиентов. И отдельно - главную ловушку повторов: двойные POST. Это та тема, где невнимательная настройка создаёт инциденты вместо того, чтобы их предотвращать. Ключевые слова, которые ты будешь гуглить в 3 часа ночи - nginx 504 timeout, nginx failover, nginx retries - после этого урока перестанут быть загадкой.

Изображение

Таймауты прокси: три рубежа и их смысл

У проксирования к бэкенду есть три фазы, и на каждую свой таймаут. Это не абстракция - это буквально три разных момента, когда что-то может зависнуть.
  • proxy_connect_timeout - сколько ждать установки TCP-соединения с апстримом. Это TCP-handshake, ещё до отправки байта запроса. Если бэкенд жив и слушает порт - укладывается в миллисекунды. Если истёк этот таймаут, значит нода либо мертва, либо сеть отвалилась, либо очередь accept переполнена. По умолчанию 60s - это вечность. Эталон - 5s. Дольше ждать установки соединения внутри одного дата-центра смысла нет: за 5 секунд здоровый бэкенд ответит сто раз.
  • proxy_send_timeout - таймаут между двумя последовательными операциями записи, когда nginx отправляет тело запроса в апстрим. Считается не на весь запрос целиком, а сбрасывается после каждой успешной записи. Важно для больших POST/PUT (загрузка файлов): если бэкенд перестал читать body, через этот таймаут nginx отвалится.
  • proxy_read_timeout - таймаут между двумя последовательными операциями чтения ответа от апстрима. Тоже не на весь ответ - он сбрасывается каждый раз, когда приходит новая порция данных. Это самый частый виновник ошибки 504. В 99 процентах случаев nginx 504 timeout - это именно proxy_read_timeout: соединение установилось, запрос ушёл, а бэкенд молчит дольше положенного. Дефолт - 60s.
Запомни ключевое про read и send: это не лимит на длительность запроса, а лимит на паузу между чанками. Стрим, который шлёт по байту каждые 9 секунд при таймауте 10s, будет жить вечно. А ответ, который генерируется 30 секунд молча, а потом приходит целиком - умрёт по таймауту, даже если бэкенд в итоге справился.

Эталонные значения для шлюза-фронта:

Код: Выделить всё

# globals/proxy.conf - общие таймауты на весь сервер
proxy_connect_timeout 5s;
proxy_send_timeout    10s;
proxy_read_timeout    10s;
Откуда 10s, а не 60? Принцип такой: глобальный таймаут должен быть строгим, потому что он защищает шлюз от лавины зависших соединений. А редкие медленные эндпоинты (генерация отчёта, выгрузка, long-polling) получают свой таймаут в своём location - это правило важнее, чем кажется. Никогда не задирай proxy_read_timeout до 600s глобально только потому, что один урл генерирует тяжёлый отчёт.

Код: Выделить всё

location /api/ {
    proxy_pass http://backend_upstream;
    # быстрые API живут на строгом глобальном 10s
}

location /reports/export {
    proxy_pass http://backend_upstream;
    proxy_read_timeout 300s;   # тяжёлая выгрузка - локальное исключение
    proxy_send_timeout 60s;
}
Ещё одно правило, которое часто забывают - согласование таймаутов по слоям. Если у тебя цепочка CDN -> фронт-nginx -> бэкенд-nginx -> PHP-FPM, то таймаут каждого внешнего слоя должен быть не меньше внутреннего. Иначе внешний nginx оборвёт соединение и отдаст клиенту 504 в тот момент, когда бэкенд уже почти закончил работу - ты потеряешь результат и впустую сожжёшь ресурсы. Считай таймауты сверху вниз: снаружи длиннее, внутри короче.

nginx proxy_next_upstream: failover и грабля двойных POST

Таймаут говорит nginx когда сдаться на конкретной ноде. А proxy_next_upstream говорит что делать дальше: попробовать следующий сервер в апстриме. Это и есть встроенный failover - бесплатный механизм отказоустойчивости nginx без всяких внешних балансировщиков.

Директива перечисляет условия, при которых запрос перебрасывается на следующий сервер:

Код: Выделить всё

# globals/proxy.conf
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries   3;
proxy_next_upstream_timeout 15s;
Разберём по словам:
  • error - ошибка при установке соединения или передаче (бэкенд отказал в соединении, оборвал его).
  • timeout - сработал один из proxy_*_timeout.
  • http_502 / http_503 / http_504 - бэкенд явно ответил кодом ошибки. Перебрасываем на соседа, который, возможно, здоров. А вот http_500 в этот список добавлять опасно: 500 часто значит баг в коде приложения, который воспроизведётся и на соседней ноде - ты просто размножишь нагрузку от заведомо падающего запроса.
  • proxy_next_upstream_tries 3 - максимум 3 попытки суммарно (включая первую). Без этого лимита при упавшем целиком апстриме nginx переберёт ВСЕ серверы по кругу на каждый запрос - и это превратит локальный сбой в шторм ретраев. Эталон - 3.
  • proxy_next_upstream_timeout 15s - общий бюджет времени на все попытки. Даже если попыток ещё не 3, после 15 секунд суммарно nginx прекращает перебор и отдаёт ошибку клиенту. Защита от ситуации, когда каждая попытка честно отрабатывает таймаут и в сумме клиент ждёт минуту.
Теперь главная ловушка этого урока. По умолчанию nginx не повторяет запросы с не-идемпотентными методами - POST, LOCK, PATCH - если запрос уже был отправлен в апстрим (поведение с версии 1.9.13). И это правильно по стандарту HTTP: прокси не должен молча ретраить не-идемпотентный запрос. Почему? Представь: nginx отправил POST /payment в бэкенд, бэкенд успел списать деньги, но при отправке ответа соединение оборвалось по timeout. nginx видит timeout, видит в списке next_upstream слово timeout - и если бы он повторил, ушёл бы второй POST /payment на соседнюю ноду. Списание дважды. Заказ дважды. Письмо отправлено дважды.

Именно поэтому есть отдельное слово - non_idempotent:

Код: Выделить всё

# ОПАСНО - так делать почти никогда не надо
proxy_next_upstream error timeout http_502 non_idempotent;
Это слово снимает защиту и разрешает повторять POST. Не включай его бездумно. Включать его допустимо ТОЛЬКО если ты на 100 процентов уверен, что эндпоинт идемпотентен на стороне приложения: либо там идемпотентный ключ (Idempotency-Key), либо операция по своей природе безопасна к повтору (upsert по уникальному ключу). На общем шлюзе для всех POST подряд non_idempotent - это прямой путь к двойным платежам.
Тонкость: защита снимается только если запрос УЖЕ отправлен в апстрим. Если соединение даже не установилось (чистый connect error, ни байта не ушло) - nginx безопасно повторит даже POST, потому что бэкенд гарантированно ничего не получил. То есть error на этапе connect ретраится всегда, а timeout после отправки POST - нет. Логично: повтор опасен ровно тогда, когда бэкенд мог что-то сделать.
Практический вывод: держи non_idempotent выключенным глобально, а если он реально нужен - включай точечно в location идемпотентного эндпоинта и больше нигде.

Circuit breaking через max_fails и связь с failfrom

proxy_next_upstream спасает один запрос - перекидывает его на здоровую ноду. Но если больная нода так и остаётся в ротации, каждый новый запрос будет сначала идти к ней, ловить таймаут и только потом уходить дальше. Это лишние секунды задержки на ровном месте. Нужен механизм, который временно выкинет больную ноду из ротации. В open-source nginx это пассивный circuit breaker через параметры сервера в апстриме:

Код: Выделить всё

# upstream.conf
upstream backend_upstream {
    server 10.0.0.1:9000 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:9000 max_fails=3 fail_timeout=30s;
    server 10.0.0.3:9000 max_fails=3 fail_timeout=30s;
    server 10.0.0.4:9000 backup;        # включится, только когда все основные легли
    keepalive 64;
}
Логика max_fails и fail_timeout связана хитрее, чем кажется по именам. Читается так: если в течение fail_timeout (30s) набралось max_fails (3) неудачных попыток - нода считается недоступной и на следующие fail_timeout (те же 30s) полностью исключается из балансировки. Через 30 секунд nginx снова пробует отправить ей запрос. Что считается неудачей - определяется тем же proxy_next_upstream: error и timeout засчитываются всегда, а http_502/503/504 - только если они перечислены в директиве. То есть max_fails и proxy_next_upstream работают в паре: список условий next_upstream одновременно говорит и куда перебрасывать запрос, и что считать провалом для circuit breaker.

Это пассивная схема: nginx узнаёт о проблеме, только когда реальный запрос на больную ноду провалится. Активных health checks (когда nginx сам периодически пингует бэкенды отдельным запросом и выводит их из ротации до того, как пострадает живой трафик) в open-source nginx нет - они есть в NGINX Plus за деньги и бесплатно в Angie (директива upstream_probe), вместе со slow_start для плавного возврата ноды после восстановления. Если активные проверки и плавный прогрев тебе нужны без подписки - это весомый аргумент в пользу Angie как форка nginx.

Грейсфул-деградация: proxy_intercept_errors и красивые ошибки

Допустим, все попытки провалились - апстрим лёг целиком. Что увидит клиент? По умолчанию nginx проксирует ответ бэкенда как есть, включая его сырую страницу ошибки 502/504 - часто это уродливый дефолтный HTML или вообще утечка стека приложения. Чтобы перехватить такие ответы и подменить их своими страницами, нужна связка proxy_intercept_errors и error_page:

Код: Выделить всё

location /api/ {
    proxy_pass http://backend_upstream;

    proxy_intercept_errors on;        # перехватываем коды >= 300 от апстрима
    error_page 502 503 504 = /errors/maintenance.html;
}

location = /errors/maintenance.html {
    root /var/www/static;
    internal;                          # доступна только внутренним редиректом
}
Без proxy_intercept_errors on директива error_page для апстрим-ответов просто не сработает - nginx отдаст оригинал от бэкенда. С ней - любой ответ бэкенда с кодом 300 и выше уходит в твой error_page. Это и есть грейсфул-деградация: вместо страшной 504 пользователь видит вежливое "идут техработы, зайдите через пару минут", а злоумышленник не получает версию и стектрейс твоего приложения. Знак "=" в error_page (= /errors/...) важен: он заставляет отдать страницу с кодом 200 вместо проксирования кода ошибки. Хочешь сохранить семантику и отдать клиенту честный 503 со своей версткой - убери "=": error_page 503 /errors/maintenance.html;

Тонкость: интерсепт ловит все коды от 300, так что аккуратно с редиректами и 404 от бэкенда - либо перечисляй конкретные коды в error_page, либо учитывай, что 404 приложения тоже будет перехвачен.

Анти-Slowloris: таймауты на клиента (аудит безопасности)

Всё выше - про защиту от медленного бэкенда. Но есть зеркальная угроза: медленный клиент. Атака Slowloris - это когда злоумышленник открывает сотни соединений и шлёт заголовки запроса по одному байту в секунду, не завершая запрос. Каждое такое соединение держит воркер занятым. Сотни медленных соединений - и легитимные клиенты не могут подключиться. Дёшево для атакующего, больно для тебя.

Защита - три клиентских таймаута, которые часто забывают выставить (включи их в чек-лист любого аудита nginx):

Код: Выделить всё

# http.conf или server-уровень
client_header_timeout 5s;     # на получение ВСЕХ заголовков запроса
client_body_timeout   10s;    # таймаут между чтениями тела запроса
send_timeout          10s;    # таймаут между записями ответа КЛИЕНТУ
  • client_header_timeout - если клиент не прислал заголовки целиком за это время, соединение рвётся. Прямой контрудар по Slowloris: байт-в-секунду больше не пройдёт.
  • client_body_timeout - то же для тела запроса. По умолчанию оба по 60s - для атакующего этого хватит, чтобы держать соединения почти бесплатно.
  • send_timeout - таймаут между записями, когда nginx отправляет ответ клиенту. Если клиент перестал читать (тоже техника медленной атаки - Slow Read), соединение освобождается.
Не путай send_timeout (ответ клиенту) с proxy_send_timeout (тело запроса бэкенду) - это разные стороны прокси. В дополнение к таймаутам стоит ограничить размеры через limit_req (rate limiting из прошлых глав) и в современном nginx 1.29.8+ появилась директива max_headers против флуда количеством заголовков. Но базовый и обязательный минимум против Slowloris - именно эти три client_*/send таймаута.

Мини-лаба: устойчивый прокси к нестабильному бэкенду

Соберём всё руками. Нужен docker или две машины. Идея: бэкенд, который умеет тормозить и падать, и nginx, который это переживает.

Шаг 1. Подними два простых бэкенда. Быстрый способ - два контейнера с любым "эхо"-сервером на портах 9001 и 9002. Один сделай заведомо медленным (например, отдающим ответ с задержкой 30 секунд) - это будет наша "больная" нода.

Шаг 2. Конфиг nginx:

Код: Выделить всё

upstream lab_backend {
    server 127.0.0.1:9001 max_fails=2 fail_timeout=20s;   # медленный
    server 127.0.0.1:9002 max_fails=2 fail_timeout=20s;   # быстрый
    keepalive 32;
}

server {
    listen 8080;

    client_header_timeout 5s;
    client_body_timeout   10s;

    location / {
        proxy_pass http://lab_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        proxy_connect_timeout 5s;
        proxy_read_timeout    3s;     # специально маленький для лабы
        proxy_send_timeout    3s;

        proxy_next_upstream         error timeout http_502 http_503;
        proxy_next_upstream_tries   2;
        proxy_next_upstream_timeout 6s;

        proxy_intercept_errors on;
        error_page 502 503 504 = /down.html;

        add_header X-Upstream-Addr $upstream_addr always;
        add_header X-Upstream-Status $upstream_status always;
    }

    location = /down.html { return 200 "backend is down, try later\n"; }
}
Шаг 3. Проверь nginx -t и перезагрузи. Теперь долби эндпоинт:

Код: Выделить всё

for i in $(seq 1 6); do curl -s -D - http://127.0.0.1:8080/ -o /dev/null | grep -i x-upstream; echo ---; done
Шаг 4. Разбери вывод. Заголовок X-Upstream-Addr показывает цепочку адресов через запятую, когда был failover - например "127.0.0.1:9001, 127.0.0.1:9002". Это значит: запрос ушёл на медленный 9001, словил read timeout за 3s, перебросился на быстрый 9002 и тот ответил. Ты видишь работу proxy_next_upstream своими глазами. После двух провалов медленной ноды (max_fails=2) она вылетит из ротации на 20s - и следующие запросы пойдут сразу на 9002 без задержки. Засеки время через curl -w "%{time_total}" - первые запросы по 3 секунды, потом мгновенные.

Шаг 5. Урони обе ноды (останови оба контейнера) и повтори запрос. Теперь все попытки провалятся, сработает error_page и ты увидишь "backend is down" вместо сырой 502. Грейсфул-деградация в действии.

Шаг 6 (опасный, для понимания). Поменяй метод на POST: curl -X POST. Убедись, что при timeout запрос НЕ ретраится (X-Upstream-Addr содержит один адрес). Потом добавь non_idempotent в proxy_next_upstream, повтори - и увидишь, что теперь POST перебрасывается. Прочувствуй, почему на платёжном эндпоинте это было бы катастрофой.

Типичные грабли
  • non_idempotent на весь шлюз ради "надёжности". Классика двойных списаний. POST по умолчанию не ретраится не от тупости nginx, а по стандарту. Снимай защиту только точечно и только для реально идемпотентных эндпоинтов.
  • proxy_read_timeout 600s глобально. Один тяжёлый отчёт - и ты задрал таймаут всем, открыв дверь для зависших соединений на всех урлах. Поднимай локально, в конкретном location.
  • Забыли proxy_next_upstream_tries. При падении всего апстрима nginx перебирает все серверы на каждый запрос - сбой превращается в шторм. Ставь лимит (эталон 3) и бюджет timeout.
  • http_500 в next_upstream. Баг приложения воспроизведётся на всех нодах - ты только размножишь падающую нагрузку. Ретрай для явных 5xx-ошибок инфраструктуры (502/503/504), а не для прикладных 500.
  • Дефолтные client_*_timeout по 60s. Открытая дверь для Slowloris. Это первое, что смотрит аудит. Урезай до 5-10s.
  • error_page без proxy_intercept_errors on. Директива есть, а не работает - клиент видит сырую ошибку бэкенда. Без интерсепта error_page не ловит апстрим-ответы.
  • Несогласованные таймауты по слоям. Внешний nginx рвёт соединение раньше, чем бэкенд закончил - результат потерян, ресурсы сожжены. Снаружи всегда не короче, чем внутри.
Контрольные вопросы
  • Запрос POST /order ушёл в бэкенд, бэкенд завис, сработал proxy_read_timeout. В директиве указано "error timeout http_502". Повторит ли nginx запрос на соседнюю ноду и почему? А если бы таймаут случился на этапе connect (соединение не установилось)?
  • В чём разница между proxy_send_timeout и send_timeout? Какой из них защищает от Slowloris?
  • proxy_read_timeout 10s. Бэкенд шлёт ответ чанками по одному байту каждые 8 секунд в течение пяти минут. Оборвётся ли соединение по таймауту? Почему?
  • Как связаны max_fails в upstream и список условий в proxy_next_upstream? Что произойдёт с нодой после max_fails=3 провалов в окне fail_timeout=30s?
Итог

Устойчивый прокси стоит на трёх опорах. Таймауты (эталон: connect 5s, send/read 10s, тяжёлые эндпоинты - локально) решают когда сдаваться на ноде. proxy_next_upstream с tries=3 и согласованным списком условий решает куда уходить, а max_fails временно гасит больную ноду. Связка proxy_intercept_errors + error_page прячет уродливые ошибки бэкенда за вежливой страницей. Клиентские таймауты client_header/body_timeout и send_timeout защищают сам nginx от медленных атак. И золотое правило, которое спасёт тебе нервы и деньги: не включай non_idempotent бездумно - двойной POST это двойное списание. Активные health checks и slow_start, если они нужны без подписки на Plus - смотри в сторону Angie.
👍2 ❤️1 🔥 😄 🤔1
Аватара пользователя
degrave
Сообщения: 1
Зарегистрирован: 26 май 2026, 16:03

Re: Таймауты, повторы и устойчивость прокси

Сообщение degrave »

Спасибо, наконец дошло почему POST не ретраится по умолчанию. У нас как раз были задвоенные заказы после того как кто-то добавил non_idempotent в конфиг чтобы 'починить failover'. Откатили.
👍1 ❤️1 🔥1 😄 🤔
Аватара пользователя
elasticuser
Сообщения: 1
Зарегистрирован: 30 май 2026, 07:32

Re: Таймауты, повторы и устойчивость прокси

Сообщение elasticuser »

Вопрос: а если read_timeout стоит 10s, но бэкенд шлёт keepalive-пинги каждые 9 секунд чтобы держать long-polling - таймаут же не сработает? Правильно понимаю что он считается между чанками а не на весь ответ?
👍 ❤️ 🔥 😄 🤔1
Ответить
← Предыдущая глава
upstream и балансировка нагрузки
Следующая глава →
FastCGI и PHP: nginx + PHP-FPM (и uwsgi/scgi)

Все главы курса «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Ошибки nginx 403, 404, 504: диагностикаОшибка nginx 502 Bad Gateway: причины и решениеupstream и балансировка нагрузки в nginxReverse proxy на nginx: proxy_pass

Вернуться в «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя