Настройка Nginx под SSE и стриминг без задержек
Буферизация Nginx превращает SSE в обычный ответ: браузер получает события пачкой или ждёт таймаута. Показываем конфигурацию для Nginx 1.25.x, проверку через curl и ошибки, из-за которых стрим «замирает».

Если Nginx буферизует ответ, SSE-соединение формально работает, но браузер получает события пачками через десятки секунд. Ниже — настройка для Nginx 1.25.x, которая занимает около 15 минут: вы подготовите прокси, отключите буферизацию только для потокового endpoint, проверите заголовки и убедитесь, что соединение не обрывается по таймауту.
Что нужно иметь до начала
Инструкция рассчитана на Nginx 1.25.x и приложение, которое уже умеет отдавать поток с типом содержимого text/event-stream. Сам Nginx не создаёт SSE-события: он только проксирует ответ и может задержать его, если оставить стандартную буферизацию.
- сервер с установленным Nginx 1.25.x или совместимой более ранней версией;
- доступ к конфигурации виртуального хоста и права на перезагрузку Nginx;
- адрес приложения, например
127.0.0.1:8000; - endpoint, например
/events, который действительно отправляет SSE; - утилита
curlдля проверки потока с сервера или локального компьютера.
Перед изменением сохраните текущую конфигурацию. Если приложение работает в Docker или Kubernetes, вместо 127.0.0.1:8000 понадобится имя сервиса и его порт внутри соответствующей сети.
Шаг 1. Отделите SSE от обычных запросов
Не отключайте буферизацию во всём виртуальном хосте. Обычные HTML-, JSON- и файловые ответы от этого не ускорятся, а расход памяти и поведение прокси станет менее предсказуемым. Для SSE выделите отдельный location.
upstream app_backend {
server 127.0.0.1:8000;
}
server {
listen 80;
server_name example.com;
location /events {
proxy_pass http://app_backend;
proxy_http_version 1.1;
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 Connection "";
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
}
}
В конфигурации замените example.com, путь /events и адрес upstream на свои значения. proxy_buffering off запрещает Nginx накапливать части ответа перед отправкой клиенту, а длинные таймауты нужны для долгоживущего соединения.
Проверка. Выполните sudo nginx -t. В ответе должны появиться строки syntax is ok и test is successful.
Шаг 2. Примените конфигурацию без остановки сервиса
После успешной проверки перечитайте конфигурацию. Перезагрузка не должна разрывать уже обслуживаемые соединения так же, как полная остановка процесса.
sudo systemctl reload nginx
Проверка. Команда должна завершиться без сообщения об ошибке. Если systemd недоступен, используйте способ управления Nginx, принятый в вашем дистрибутиве, но сначала всё равно запускайте nginx -t.
Шаг 3. Проверьте поток без браузера
Браузер скрывает часть сетевых деталей, поэтому сначала проверьте endpoint через curl. Ключ -N отключает буферизацию вывода со стороны самого curl.
curl -N -i --http1.1 https://example.com/events
В заголовках ожидайте Content-Type: text/event-stream. Затем приложение должно присылать строки формата data: ..., завершённые пустой строкой. Если сервер отправляет только комментарии для поддержания соединения, они выглядят как строки, начинающиеся с двоеточия.
Проверка. После запуска команды новые события должны появляться по одному, с задержкой, близкой к задержке на стороне приложения. Не закрывайте соединение сразу: оставьте его открытым хотя бы на время проверки heartbeat.
Шаг 4. Настройте heartbeat на стороне приложения
Nginx не знает, жив ли клиент, если приложение долго не отправляет данные. Поэтому сервер SSE должен периодически посылать небольшой комментарий или служебное событие. Интервал выбирайте меньше самого короткого таймаута между клиентом, прокси, балансировщиком и сетевым экраном.
Пример логики в этом материале намеренно не привязан к фреймворку: названия методов генерации ответа различаются в Django, Node.js, Go, PHP и других стеках. Важно не имя функции, а три свойства: ответ не закрывается после первого сообщения, каждая запись заканчивается пустой строкой, а данные сбрасываются в сеть сразу.
: heartbeat
data: {"status":"ready"}
Символы перевода строки в примере значимы. Одинарный перевод строки завершает поле события, пустая строка завершает само событие. Если фреймворк использует собственный буфер ответа, отключите или уменьшите его средствами именно этого фреймворка.
Проверка. В выводе curl -N должны регулярно появляться heartbeat-сообщения, даже когда бизнес-событий нет.
Шаг 5. Добавьте заголовки там, где это нужно
Тип text/event-stream должен выставлять backend. Запасной вариант — добавить его на уровне Nginx, но так легко скрыть ошибку приложения, которое отдаёт неправильный формат. Заголовок Cache-Control: no-cache обычно уместен для SSE, однако настройки CDN и внешнего балансировщика нужно проверить отдельно.
Некоторые приложения возвращают X-Accel-Buffering: no. Это специальный ответный заголовок Nginx, который также отключает буферизацию для конкретного ответа. Он полезен, если один и тот же endpoint может работать через разные прокси, но не заменяет проверку всей цепочки доставки.
Для SSE важна не только скорость первого байта. Важнее, чтобы каждый следующий байт проходил через всю цепочку до клиента без накопления в буфере.
Если поток не работает
curl подключается, но данных нет
Проверьте backend напрямую: curl -N -i http://127.0.0.1:8000/events. Если и так нет событий, проблема находится в приложении, а не в Nginx. Если напрямую поток идёт, сравните путь в proxy_pass: при слеше в конце Nginx может по-разному передать URI в зависимости от структуры location.
События приходят пачкой
Сначала убедитесь, что используется именно тот location, где задано proxy_buffering off. Затем проверьте буфер приложения, Gunicorn или uWSGI, PHP-FPM, CDN и балансировщик. Один включённый буфер после Nginx способен вернуть задержку, даже если конфигурация прокси корректна.
Соединение закрывается примерно через минуту
Ищите таймаут с таким значением в Nginx, ingress-контроллере, облачном балансировщике или на клиенте. proxy_read_timeout задаёт ожидание данных от upstream, но не отменяет ограничения внешнего прокси. Heartbeat должен приходить чаще этого лимита.
В браузере ошибка CORS
Если frontend и SSE endpoint находятся на разных origin, браузер применяет CORS. Настройте разрешённый origin на приложении или на прокси, но не подставляйте без разбора значение *, если запросы используют учетные данные. Проверяйте фактические заголовки через вкладку Network и ответ curl.
Вместо потока приходит 502 Bad Gateway
Nginx не может подключиться к upstream либо приложение закрыло соединение. Проверьте адрес и порт, доступность процесса, журналы Nginx и лимиты количества открытых соединений. Ошибка DNS или имя контейнерного сервиса также проявится как проблема upstream, хотя сам SSE здесь ни при чём.
Где такая настройка ломается
Отключение буферизации решает только задержку между Nginx и клиентом. Оно не исправляет очередь событий, блокирующий обработчик, лимит файловых дескрипторов, перезапуск worker-процесса или прокси перед Nginx. У SSE есть и архитектурное ограничение: каждый клиент держит отдельное длительное соединение, поэтому под высокой аудиторией нужно отдельно считать workers, соединения, память и лимиты операционной системы.
Если поток проходит через CDN, ingress или несколько балансировщиков, тестируйте не только локальный адрес приложения. Проверяйте полный маршрут с тем же доменом и TLS, которым пользуется браузер. Для HTTP/2 поведение зависит от всей цепочки; настройка proxy_http_version 1.1 относится к соединению Nginx с upstream и не означает, что клиент обязательно подключён по HTTP/1.1.
Что проверить после настройки
- Запишите время отправки события в лог приложения и время получения на клиенте. Так вы увидите задержку, а не будете оценивать её по ощущениям.
- Проверьте открытие нескольких одновременных SSE-соединений и посмотрите на число файловых дескрипторов, память и количество worker-процессов.
- Протестируйте перезапуск приложения и Nginx: клиент должен корректно переподключаться, а сервер — освобождать старые соединения.
- Настройте метрики активных подключений, частоты отключений и времени до первого события. Инвалидация и повторная доставка пропущенных событий — отдельная тема, здесь рассматривается только прохождение потока через Nginx.
Сноска. Heartbeat — периодическое служебное сообщение без бизнес-данных. Оно поддерживает соединение активным и помогает обнаружить разрыв, но не заменяет механизм повторной доставки событий.
Проверка рекламы на риски: Что в макете вызовет вопросы ФАС: превосходные степени, сравнения, обещания результата, маркировка.
Спросить за 40 ₽Источники: Nginx ngx_http_proxy_module: proxy_buffering, таймауты и проксирование, MDN: Using server-sent events
Частые вопросы
Нужно ли отключать proxy_buffering для всего сайта?
Почему SSE работает через curl, но зависает в браузере?
Какой таймаут поставить для SSE?
Нужен ли proxy_http_version 1.1?
Что делать, если у меня другая версия Nginx?
Достаточно ли Nginx, чтобы не терять события при переподключении?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.