WebSocket за nginx обрывается: как проверить 101 и idle timeout

План диагностики WebSocket за reverse proxy: от ответа 101 до idle timeout, ping/pong и сверки журналов по времени.

Если WebSocket за nginx соединяется, но затем обрывается, разделите диагностику на два этапа: HTTP-handshake и жизнь уже открытого канала. Нет ответа 101 Switching Protocols — проверяйте маршрут, TLS, авторизацию и заголовки Upgrade. Ответ 101 есть, а разрыв повторяется после одинаковой паузы — ищите idle timeout в цепочке прокси и отсутствие ping/pong.

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

Сначала определите, на каком этапе ломается соединение

Откройте в браузере DevTools → Network → WS, перезагрузите страницу и выберите WebSocket-запрос. Зафиксируйте URL, время начала, HTTP-код, выбранный subprotocol, последние входящие и исходящие кадры и код закрытия. Cookie, токены и содержимое приватных сообщений в журнал или заявку не копируйте.

| Наблюдение | Где искать сначала | |---|---| | Нет запроса WS | клиентский код, условие запуска, CSP, ошибка JavaScript | | 400, 403 или 404 вместо 101 | путь, Origin, авторизация, маршрут приложения | | 502 или 504 | доступность upstream, порт, DNS между nginx и приложением | | Есть 101, затем мгновенный разрыв | subprotocol, расширения, ошибка обработчика, первый кадр | | Есть 101, разрыв после повторяемой паузы | таймауты nginx, ingress, балансировщика, CDN и ping/pong |

По RFC 6455 любой HTTP-код, кроме 101, означает, что WebSocket-handshake не завершён. Сам код 101 подтверждает только переключение протокола: он ещё не доказывает, что сообщения ходят в обе стороны и соединение переживает рабочие паузы.

Шаг 1. Повторите handshake без интерфейса сайта

Для публичного тестового endpoint можно отправить ограниченный запрос без cookies и секретов:

curl --http1.1 -i --max-time 5 \
  -H 'Connection: Upgrade' \
  -H 'Upgrade: websocket' \
  -H 'Sec-WebSocket-Version: 13' \
  -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
  https://example.ru/ws

Замените адрес на свой публичный wss://-endpoint в форме https:// для HTTP-handshake. Такой запрос не воспроизводит браузерную авторизацию, Origin, выбранный subprotocol и дальнейший обмен кадрами. Поэтому 101 в curl — полезная контрольная точка, но не итоговый тест пользовательского сценария.

Если браузер получает 403, а запрос без Origin — 101, проверьте разрешённые Origin и способ передачи сессии. Если nginx отвечает 404, сопоставьте точный путь, завершающий слеш и правило location. Если приходит 502, продолжите по отдельному разбору 502 Bad Gateway: сначала нужно восстановить связь прокси с upstream.

Шаг 2. Проверьте передачу Upgrade через nginx

Upgrade и Connection относятся к hop-by-hop заголовкам и не передаются upstream автоматически. В соответствующем location обычно нужны HTTP/1.1 и явная передача заголовков:

location /ws/ {
    proxy_pass http://websocket_backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Это схема, а не готовый фрагмент для слепого копирования. Сверьте имя upstream, путь после proxy_pass, Host, TLS до backend и правила авторизации. Официальная документация nginx также показывает вариант через map, когда один location обслуживает обычные и обновляемые соединения.

После изменения проверьте не только наличие 101, но и отправку сообщения клиентом, ответ сервера и корректное закрытие. Перезапускать nginx стоит только после проверки синтаксиса и с планом отката.

Шаг 3. Найдите самый короткий idle timeout

Измерьте интервал между последним полезным кадром и разрывом. Если он стабильно близок к одному значению, составьте цепочку соединения: браузер → CDN/WAF → внешний балансировщик → ingress/nginx → приложение. У каждого звена может быть свой предел бездействия, а фактически сработает самый короткий.

У nginx proxy_read_timeout ограничивает паузу между чтениями от proxied server; в официальной документации для WebSocket указан стандартный разрыв после 60 секунд тишины и возможность увеличить таймаут. Но просто поставить очень большое значение недостаточно: долгоживущие соединения занимают ресурсы, а внешнее звено всё равно может закрыть канал раньше.

Практичнее согласовать три величины:

  1. максимально допустимую рабочую паузу;
  2. интервал ping/pong заметно короче минимального таймаута в цепочке;
  3. таймаут обнаружения зависшего клиента, после которого соединение закрывается осознанно.

TCP keepalive не заменяет WebSocket ping/pong: сетевое соединение может считаться живым, пока приложение уже не обменивается сообщениями. Ping должен создавать реальный трафик через всю прокси-цепочку, а клиент — обрабатывать потерю pong и переподключаться с ограниченной задержкой, без бесконечного частого цикла.

Шаг 4. Локализуйте слой сравнением трёх маршрутов

Проводите обходные тесты только из разрешённой сети, не публикуя внутренний адрес и не отключая защиту для всего интернета.

  1. Проверьте публичный URL через всю цепочку.
  2. Обойдите CDN или внешний балансировщик, но оставьте тот же nginx.
  3. Из внутренней точки подключитесь прямо к приложению.

Если прямое соединение стабильно, а через nginx обрывается, сравните заголовки, proxy_read_timeout и журналы nginx. Если nginx стабилен, а публичный маршрут нет, проверяйте лимиты CDN/WAF или балансировщика. Если падают все три варианта, причина вероятнее в приложении, его event loop, лимитах или логике heartbeat.

Сопоставляйте события по времени и безопасному request ID: начало handshake, ответ 101, последний кадр, закрытие на каждом слое. Строка доступа с 101 без длительности и причины закрытия не подтверждает успешный сеанс.

Что проверить перед закрытием инцидента

  • handshake возвращает 101 на реальном пути пользователя;
  • сообщения проходят в обе стороны;
  • соединение переживает паузу дольше обычной рабочей;
  • ping/pong обнаруживает зависший канал;
  • переподключение не создаёт шторм запросов;
  • проверка повторена после релиза и хотя бы из затронутой сети.

Открывающаяся главная страница, единичный ответ 101 или автоматическое переподключение интерфейса не доказывают, что WebSocket исправен. Обычные публичные URL можно контролировать в Web-Puls, но такой контроль не заменяет отдельный сценарий для долгоживущего канала и обмена кадрами.

Если разрыв проходит через несколько прокси и владельца таймаута определить не удаётся, отправьте публичный URL, время с часовым поясом, интервал до обрыва и обезличенные фрагменты журналов через форму профессиональной поддержки. Пароли, cookies, токены и приватные адреса в заявку добавлять не нужно.

Проверьте свой сайт прямо сейчас

Введите адрес сайта: Web-Puls покажет HTTP-код, время ответа и базовую диагностику. Для постоянного контроля можно подключить мониторинг.

Нужна помощь с диагностикой ошибки?

Опишите симптомы в короткой заявке: URL, код ответа, время появления и что менялось перед сбоем. Специалист поддержки оценит задачу и предложит формат работ.