Tedval

Nginx буферизует, когда его просили не буферизовать

21 июля 2026

Симптом был такой: приложение отдаёт ответ кусками по мере готовности, локально всё приходит сразу, а через прокси клиент ждёт несколько секунд и получает всё разом. Классика, но интересны детали — выключить буферизацию одной директивой оказалось недостаточно.

Две стороны, а не одна

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

ДирективаЧто делает
proxy_buffering offОтвет идёт клиенту по мере поступления, а не копится в буфере
proxy_request_buffering offТело запроса передаётся приложению потоком, nginx не ждёт его целиком

Для загрузок важна вторая: без неё nginx сначала примет весь файл на диск и только потом начнёт разговор с приложением. На больших файлах это выглядит как зависание в начале.

Промежуточные буферы

Даже с выключенной буферизацией ответа остаётся размер буфера, в который nginx читает данные из соединения. Пока он не заполнен или не закрыт поток, клиенту может ничего не уходить. Помогает уменьшение:

proxy_buffer_size 4k;
proxy_buffers 8 4k;

Плюс явное отключение сжатия на лету для таких маршрутов — gzip off. Сжатие по своей природе требует накопления, иначе экономии не получится, и это ещё один источник задержки.

Не забыть про HTTP-версию

По умолчанию nginx общается с апстримом по HTTP/1.0, где нет постоянных соединений и chunked-передачи. Для стриминга это неприемлемо:

proxy_http_version 1.1;
proxy_set_header Connection "";

Пустой заголовок Connection нужен, чтобы не пробрасывать клиентский keep-alive или close в апстрим и не рвать соединение раньше времени.

Как измерять

Проверять глазами бесполезно, нужны цифры. У curl есть готовые переменные:

curl -o /dev/null -s -w \
  "connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n" \
  https://example.test/stream

Ключевой показатель — time_starttransfer, время до первого байта. Если оно почти равно time_total, значит ответ пришёл одним куском в конце, и буферизация где-то осталась. Если между ними заметный разрыв — поток идёт как задумано.

Отдельно стоит проверить proxy_read_timeout. Для долгих потоков дефолтные шестьдесят секунд означают обрыв ровно посередине, причём в логе это выглядит как ошибка апстрима, хотя апстрим ни при чём.

Итоговый блок

location /stream/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering off;
    proxy_request_buffering off;
    proxy_read_timeout 300s;
    gzip off;
}

После этого время до первого байта упало до сотни миллисекунд, а данные пошли ровным потоком.Полдня на поиск, четыре строки в конфиге — обычная пропорция для таких историй.

← Ротация логов Продление сертификатов →