Заголовки, таймауты и буферы проксирования

Базовый proxy_pass работает, но прод требует деталей: правильные заголовки, разумные таймауты и буферы, иначе один медленный бэкенд утянет всё.
«Таймаут — это не пессимизм, это страховка. Без него зависший бэкенд держит соединения вечно.»

В прошлом уроке мы подняли прокси. Теперь сделаем его надёжным. Три темы: заголовки (что доносим до бэкенда), таймауты (когда сдаёмся) и буферы (как не задушить бэкенд медленным клиентом).

Заголовки проксирования

proxy_set_header Host              $host;
proxy_set_header X-Real-IP         $remote_addr;
proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host  $host;

Переменная $proxy_add_x_forwarded_for аккуратно дописывает IP клиента в конец уже существующей цепочки X-Forwarded-For (если запрос прошёл через несколько прокси). $scheme сообщает бэкенду, был ли запрос по http или https — критично, когда Nginx терминирует TLS, а бэкенд работает по http.

Таймауты

proxy_connect_timeout 5s;    # сколько ждать установки соединения с бэкендом
proxy_send_timeout    60s;   # пауза при отправке запроса бэкенду
proxy_read_timeout    60s;   # пауза при чтении ответа бэкенда

Без таймаутов зависший бэкенд держит соединение бесконечно, и они копятся, пока Nginx не упрётся в лимиты. proxy_connect_timeout ставь небольшим (3–5s): если бэкенд не отвечает на коннект быстро — он, скорее всего, мёртв.

Буферизация

proxy_buffering on;
proxy_buffers 8 16k;        # 8 буферов по 16 КБ на ответ
proxy_buffer_size 16k;      # буфер под заголовки ответа

Обработка ошибок бэкенда

location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_next_upstream error timeout http_502 http_503;
    error_page 502 503 504 /maintenance.html;
}
location = /maintenance.html {
    root /var/www/errors;
    internal;
}

Как работает под капотом

При proxy_buffering on Nginx читает ответ бэкенда в свои буферы и, как только бэкенд закончил, освобождает его — даже если клиент ещё качает ответ по медленному мобильному интернету. Так один воркер приложения обслуживает больше запросов в секунду. Если ответ не влезает в память, Nginx сбрасывает остаток во временный файл на диск. Для стриминга (Server-Sent Events, длинные потоки) буферизацию, наоборот, отключают: proxy_buffering off;.

Частые ошибки

  • Дефолтный proxy_read_timeout 60s для долгих операций. Тяжёлый отчёт на 90 секунд оборвётся на 60-й. Подними таймаут именно для этого location.
  • Буферизация при стриминге. SSE/стрим «залипают», потому что Nginx ждёт полного ответа. Отключи proxy_buffering для таких маршрутов.
  • Грязный X-Forwarded-For. Если доверять заголовку от клиента вслепую, его можно подделать. За доверенными прокси используй $proxy_add_x_forwarded_for и настрой real_ip.

Best practices

  • Короткий proxy_connect_timeout (3–5s), разумные send/read под характер маршрута.
  • Красивая error_page для 502/503/504 — пользователь увидит «идут работы», а не голую ошибку Nginx.
  • Для стриминговых эндпоинтов — proxy_buffering off точечно.

Восстановление реального IP клиента

Передать заголовки мало — бэкенд должен ещё и правильно их прочитать, а это отдельная тема. Когда Nginx проксирует запрос, для приложения источником становится сам Nginx (127.0.0.1 или адрес внутренней сети). Реальный IP клиента уезжает в X-Forwarded-For, но приложение увидит его только если настроено доверять этому заголовку. Если бэкенд логирует remote_addr «как есть», все запросы будут выглядеть пришедшими с localhost — бесполезно для аналитики, антифрода и блокировок.

У Nginx для этого есть модуль real_ip: директивы set_real_ip_from (каким прокси доверять) и real_ip_header X-Forwarded-For заставляют сам Nginx подставить настоящий IP клиента в $remote_addr — удобно, когда перед Nginx стоит ещё и CDN вроде Cloudflare. Тут же кроется и риск безопасности: X-Forwarded-For легко подделать, поэтому доверять ему можно только от известных, контролируемых прокси, а от внешнего клиента — никогда. Иначе атакующий пропишет себе любой IP и обойдёт ограничения по адресу. Правило простое: настрой доверенные диапазоны явно, и тогда цепочка X-Forwarded-For станет надёжным источником правды о том, кто на самом деле к тебе пришёл.

Итоги

Надёжный прокси = правильные заголовки (Host, X-Forwarded-*), осмысленные таймауты (быстрый connect, адекватные read/send под нагрузку), буферизация для защиты бэкенда от медленных клиентов и красивые страницы ошибок. Точечно отключай буферизацию для стриминга. Дальше — апстримы и пулы соединений, фундамент балансировки.

Проверьте себя
1. Зачем нужен короткий proxy_connect_timeout (3–5 секунд)?
AЧтобы ускорить ответ бэкенда
BЕсли бэкенд не принимает соединение быстро, он, скорее всего, мёртв — нет смысла ждать долго
CЧтобы включить HTTPS
DЧтобы сжать ответ
2. Почему для Server-Sent Events стоит отключить proxy_buffering?
AДля безопасности
BПри включённой буферизации Nginx ждёт ответ целиком, и поток событий «залипает» вместо постепенной отдачи
CИначе не работает HTTPS
DЭто ускоряет статику