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

WebSocket за Nginx обычно подключается без проблем, а затем внезапно закрывается через 60 секунд тишины. Это не обязательно ошибка приложения: стандартный proxy_read_timeout Nginx ограничивает паузу между данными от upstream. Ниже — конфигурация для Nginx 1.25 и новее, разбор таймаутов, пути запроса и диагностика случаев, когда в логах появляются 502 или 504.
Что именно нужно настроить
Обычный HTTP-прокси не превращается в WebSocket автоматически. Клиент сначала отправляет HTTP-запрос с заголовками Upgrade: websocket и Connection: Upgrade. Сервер отвечает статусом 101 Switching Protocols, после чего соединение переходит в двунаправленный режим.
Nginx должен передать эти заголовки upstream. В конфигурации ниже также задаётся версия HTTP/1.1: без неё проксирование WebSocket не заработает.
Таймаут WebSocket — это не «время жизни соединения». Для Nginx это максимальная пауза между операциями чтения от сервера, если приложение ничего не отправляет.
Базовая схема конфигурации
Блок map размещается внутри секции http. Он позволяет отправлять Connection: upgrade только для запросов, в которых клиент действительно прислал заголовок Upgrade. Для остальных HTTP-запросов Nginx передаст значение close.
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream websocket_backend {
server 127.0.0.1:8080;
}
server {
listen 443 ssl;
server_name example.com;
location /socket/ {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
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_read_timeout 10m;
proxy_send_timeout 10m;
proxy_connect_timeout 5s;
proxy_buffering off;
}
}
}
После изменения проверьте синтаксис командой nginx -t, затем перечитайте конфигурацию штатной командой вашей системы управления сервисами. Успешная проверка должна закончиться сообщением syntax is ok и test is successful. Само подключение считается успешным, если клиент получает HTTP-ответ 101.
Сноска. Upstream — сервер или группа серверов, к которым Nginx передаёт запрос. В этой статье это приложение на 127.0.0.1:8080, а не браузер клиента.
Как работают таймауты
У WebSocket нет отдельного «таймаута WebSocket» в Nginx. На результат влияют несколько параметров, и каждый отвечает за свой этап.
| Параметр | Что ограничивает | Когда проявляется | Практический ориентир |
|---|---|---|---|
proxy_connect_timeout |
Время установления TCP-соединения с upstream | Приложение недоступно, порт закрыт, сеть не отвечает | Обычно несколько секунд, например 5s |
proxy_read_timeout |
Пауза между операциями чтения от upstream | Канал подключился, но сервер долго не отправляет данные | Дольше ожидаемого интервала heartbeat, например 10m |
proxy_send_timeout |
Пауза при отправке запроса в upstream | Upstream медленно принимает данные или зависает при чтении | Подбирается по размеру сообщений и нагрузке |
Значение proxy_read_timeout 10m не гарантирует, что соединение проживёт десять минут, а затем закроется. Если приложение регулярно отправляет ping, pong или данные, таймер бездействия сбрасывается. Если сервер молчит дольше десяти минут, Nginx может закрыть канал.
Завышать таймаут до нескольких часов без причины опасно. Тысячи зависших подключений будут дольше занимать память, файловые дескрипторы и соединения с upstream. Если приложение поддерживает heartbeat, лучше согласовать таймаут с его интервалом и запасом, а не просто поставить максимально большое значение.
Три места, где конфигурация ломается
Заголовки Upgrade не дошли до приложения
Заголовки Upgrade и Connection относятся к hop-by-hop-заголовкам. Nginx не обязан передавать их upstream автоматически, поэтому их задают явно через proxy_set_header. Если этого нет, приложение может ответить обычным HTTP-ответом вместо 101.
Проверьте DevTools в браузере: во вкладке Network запрос WebSocket должен иметь статус 101 Switching Protocols. Ответ 200, 400 или 426 означает, что проблема находится на этапе handshake, а не в таймауте.
Путь изменился из-за proxy_pass
Слеш в конце proxy_pass меняет путь, который увидит приложение. При такой конфигурации:
location /socket/ {
proxy_pass http://websocket_backend;
}
запрос /socket/chat обычно передаётся upstream с тем же URI — /socket/chat. Если написать:
location /socket/ {
proxy_pass http://websocket_backend/;
}
Nginx заменит совпавшую часть /socket/ на /, и приложение получит /chat. Это удобно, когда backend слушает корневой путь, но ломает маршрутизацию, если он ожидает полный префикс.
Выберите один вариант и проверьте фактический URI в access-логе приложения. Не исправляйте такую проблему увеличением таймаута: неверный маршрут не станет рабочим от долгого ожидания.
Таймаут выставлен только на одном уровне
Между браузером и WebSocket-сервисом могут стоять CDN, облачный балансировщик, ingress-контроллер и ещё один Nginx. Любой из них способен закрыть канал раньше. Например, значение proxy_read_timeout 10m бесполезно, если внешний балансировщик разрывает неактивное соединение через 60 секунд.
Составьте цепочку маршрута и выпишите таймаут простоя на каждом звене. Минимальное значение обычно и определяет фактическое время жизни молчащего соединения.
Как выбрать между heartbeat и большим таймаутом
| Подход | Плюсы | Цена и риск | Когда выбирать |
|---|---|---|---|
| Heartbeat на уровне WebSocket | Обнаруживает мёртвые клиенты и поддерживает канал активным | Нужна поддержка ping/pong в сервере и клиенте | Постоянные соединения, мобильные клиенты, балансировщики |
Увеличенный proxy_read_timeout |
Меняет конфигурацию без доработки протокола | Зависшие каналы дольше занимают ресурсы | Редкие события и небольшой объём подключений |
| Отключение неактивных сессий | Предсказуемо ограничивает расход ресурсов | Клиент должен уметь переподключаться и восстанавливать состояние | Чаты, панели и подписки, где реконнект предусмотрен архитектурой |
Для рабочих систем обычно нужен heartbeat, а Nginx-таймаут служит дополнительной страховкой. Сам по себе длинный таймаут не доказывает, что клиент жив: TCP-соединение может сохраняться после потери сети, пока операционная система или промежуточный узел не обнаружит разрыв.
Что проверять по логам
- Статус handshake. Ищите
101. Если его нет, проверяйте URI, заголовки Upgrade, схемуws/wssи маршрут до приложения. - Время закрытия. Разрыв почти ровно через 60 секунд указывает на значение таймаута по умолчанию или внешний прокси. Разрыв через заданные десять минут — на
proxy_read_timeoutлибо такой же лимит выше по цепочке. - Ошибку Nginx.
upstream timed outозначает, что Nginx ждал ответ или данные от upstream дольше допустимого. Это не всегда означает падение приложения. - Состояние backend. Проверьте число открытых соединений, лимиты файловых дескрипторов и обработку ping/pong. Если приложение само закрывает сокет, изменение Nginx ничего не исправит.
Для диагностики временно добавьте в формат access-лога время запроса и статус, но не включайте отладочный лог на постоянной основе под нагрузкой. В журналах не должны оказаться токены авторизации, содержимое сообщений и персональные данные.
Где подход ломается
Одна настройка Nginx не решает проблемы, которые находятся в другом слое. WebSocket может закрываться из-за лимита облачного балансировщика, перезапуска процесса, исчерпания файловых дескрипторов, неверной TLS-конфигурации или отсутствия реконнекта на клиенте.
Есть и архитектурный предел: если несколько экземпляров приложения обслуживают WebSocket, клиент после переподключения может попасть на другой экземпляр. Для чата, уведомлений и подписок понадобятся общая шина событий или иная схема доставки сообщений. Это отдельная тема; здесь рассматривается только проксирование через Nginx.
Что сделать дальше
Сначала зафиксируйте ожидаемый интервал heartbeat и максимальное время восстановления соединения. Затем сопоставьте его с proxy_read_timeout и лимитами всех балансировщиков. После выкладки измеряйте долю успешных ответов 101, число активных WebSocket-соединений, количество реконнектов и разрывы по причине таймаута. Такой набор метрик быстрее покажет, проблема в handshake, маршруте, простое канала или самом приложении.
Субтитры и таймкоды: Расшифровка речи и субтитры по правилам читаемости: 42 знака в строке, две строки, минимум секунда на реплику.
Спросить за 40 ₽Источники: WebSocket proxying, документация Nginx, ngx_http_proxy_module, документация Nginx
Частые вопросы
Почему WebSocket закрывается ровно через 60 секунд?
Как понять, что Nginx правильно проксирует WebSocket?
Нужно ли ставить proxy_read_timeout на несколько часов?
Что делать, если после настройки появляется 502?
Сработает ли эта конфигурация на другой версии Nginx?
Почему соединение закрывается, хотя timeout в Nginx увеличен?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.