WebSocket не подключается или обрывается: проверка Nginx и прокси
Чат открывается, но сообщения не приходят. Кабинет показывает прежний статус, пока пользователь не обновит страницу. В Console появляется WebSocket connection failed, хотя обычные страницы сайта доступны. Чтобы понять, почему WebSocket не работает через Nginx, нужно проверить отдельный путь постоянного соединения: адрес, согласование протокола, промежуточные узлы и обмен после подключения.
Увеличение тайм-аута помогает только при определённой причине. Если запрос попал в обработчик HTML, прокси не передал Upgrade или приложение отвергло сессию, долгое ожидание ничего не исправит. Ниже разберём последовательную диагностику, ограниченный пример настройки Nginx и проверку восстановления после обрыва. Цель — подтвердить получение нужных данных, а не только появление зелёного индикатора соединения.
Сначала определите этап отказа
Разделите три ситуации. Соединение не установилось; установилось и закрылось через некоторый интервал; остаётся открытым, но нужные сообщения не приходят. Эти наблюдения ведут к разным гипотезам. Например, отсутствие успешного рукопожатия направляет проверку к маршруту и разрешению подключения, а обрыв после периода молчания — к ограничениям ожидания и heartbeat.
Дальнейший пример относится к классическому переходу с HTTP/1.1 на WebSocket. Для него успешное согласование включает ответ 101 Switching Protocols. Это отдельный обмен по адресу соединения, поэтому статус 200 у главной страницы ничего не говорит о готовности чата. При этом даже правильный 101 подтверждает только установление канала, а не подписку на нужные события.
Не переносите этот признак на любой транспорт без проверки. RFC 8441 описывает WebSocket поверх HTTP/2 через расширенный CONNECT, где процедура отличается. Уточните протокол на исследуемом участке: браузер, внешний прокси и соединение с приложением могут использовать разные варианты. Пример Nginx ниже явно задаёт HTTP/1.1 до upstream.
Запишите ожидаемое действие: какое сообщение должно появиться, откуда оно возникает и сколько допустимо ждать. Без такого условия открытый сокет легко принять за исправный механизм обновления, хотя приложение ещё не подтвердило подписку.
Снимите наблюдения в браузере
Откройте DevTools до повторения сценария. В Network выберите фильтр WS, найдите нужное соединение и посмотрите URL, инициатор и заголовки. Затем откройте Messages. Справочник Chrome DevTools описывает просмотр сообщений, их времени и направления. Сравните момент подключения, первое полезное сообщение и момент отказа.
Если нужной строки нет, вернитесь к Console и коду, который должен создать WebSocket. Проверяйте конкретную попытку: другое соединение аналитики или служебного инструмента не подтверждает работу приложения. Убедитесь, что фильтры и сохранение журнала позволяют увидеть исходную последовательность, включая переход на страницу.
Для обрыва полезны время, код закрытия, доступная причина и события на стороне приложения. В описании CloseEvent.code значение 1006 обозначает ненормальное закрытие без полученного кадра Close. Это наблюдение клиента, а не установленная причина «сломался Nginx». Сам сервер не должен посылать 1006 как обычный код закрывающего кадра.
Сохраните идентификатор подключения или запроса, который можно сопоставить с журналами. Перед передачей материалов уберите токены, cookies и содержимое личных сообщений. Для первичного разбора чаще нужны адрес без секретов, временные отметки, статусы и направление обмена.
Проверьте адрес, безопасное соединение и допуск
Сопоставьте фактический URL с адресом, предусмотренным приложением: схема, хост, порт, путь и параметры должны соответствовать окружению. В руководстве MDN по клиенту WebSocket для рабочего приложения рекомендуется HTTPS-страница и защищённое соединение WSS. Ошибка сертификата или попытка небезопасного подключения требует исправления соответствующего участка.
Не подменяйте проверку отключением проверки сертификата. Убедитесь, что имя соответствует сертификату, а TLS завершается на ожидаемом узле. Если соединение вообще не доходит до приложения, бессмысленно начинать с его обработчика сообщений. Сначала найдите последний участок, на котором попытка действительно наблюдается.
Проверьте путь после проксирования. Приложение может ждать /ws/, а получать / или адрес другой версии API. Сравните маршрут браузера с журналом upstream. Отдельно подтвердите согласованный подпротокол, если библиотека клиента его использует: WebSocket задаёт транспорт, но формат сообщений и дополнительные условия определяет приложение.
Сессия и допустимый Origin также могут влиять на решение сервера принять соединение. Не убирайте эти проверки ради 101. Установите, какие полномочия требуются и что пришло в реальном запросе. Блокировку политикой страницы рассматривают отдельно; добавление заголовков CORS по аналогии с обычным fetch не является универсальным ремонтом WebSocket.
Проследите всю цепочку прокси
Составьте фактический маршрут: браузер, CDN или защитный сервис, балансировщик, Nginx, приложение. Для каждого участка укажите ответственного, поддержку выбранного способа подключения и ограничения длительных соединений. Если WebSocket работает в тестовой среде без внешнего прокси, это помогает локализации, но не подтверждает исправность рабочего маршрута.
Сравнивайте контролируемые проверки с одинаковыми путём, полномочиями и поведением клиента. Успешный тест с другим Host или без авторизации может исследовать совсем другой обработчик. Прямой доступ к backend используют только там, где он предусмотрен архитектурой и разрешён: открывать закрытый сервер в интернет ради диагностики не требуется.
Сопоставьте журналы по времени и идентификаторам. Дошла ли попытка до Nginx? Какой upstream выбран? Получило ли приложение запрос и что вернуло? Если приложение не видит обращения, ошибка находится раньше либо запрос попал в другой экземпляр. Если оно отказало, сначала разберите его решение, а не увеличивайте ожидание на прокси.
Меняйте один подтверждённый участок за раз и повторяйте тот же сценарий. Одновременное изменение CDN, маршрутов и приложения затруднит объяснение результата. Сохраните исходную конфигурацию и установите, какие наблюдения должны измениться после конкретной правки.
Проверьте передачу Upgrade в Nginx
Официальная инструкция Nginx по WebSocket объясняет, почему заголовки Upgrade и Connection передают явно: они относятся к отдельному участку соединения. Следующие фрагменты показывают учебный маршрут к приложению на 127.0.0.1:8080, которое ожидает путь /ws/ и исходное имя сайта. Предполагается уже настроенный HTTPS-сервер.
Первый фрагмент размещается в контексте http, вне блока server:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
Второй относится к соответствующему существующему блоку server. Это пример для адаптации, а не замена всей конфигурации:
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 75s;
}
Версия до upstream указана явно. Значения по умолчанию менялись между выпусками Nginx; не определяйте их по случайному старому примеру. Значение 75 секунд здесь условное. В показанном простом маршруте proxy_pass без URI сохраняет исходный путь запроса, поэтому приложение получает /ws/, а не корень.
Учтите правила модуля proxy: директивы proxy_set_header наследуются с предыдущего уровня только при отсутствии таких директив на текущем. Сохраните необходимый проекту полный набор заголовков, включая ранее настроенные, и согласуйте Host с upstream. После проверки конфигурации подтвердите передачу реального запроса через весь маршрут.
Разберите ответ до перехода на WebSocket
Если ожидаемый HTTP/1.1 handshake не завершился, сохраните ответ и определите, какой узел его сформировал. Код помогает выбрать направление, но не заменяет журнал приложения. Одинаковый статус может прийти от защитного сервиса, самого Nginx или backend.
| Наблюдение | Направление проверки |
|---|---|
| 200 и HTML | Запрос попал на обычную страницу или запасной маршрут |
| Перенаправление | Проверить URL, завершение TLS и переход на авторизацию |
| 401 или 403 | Определить отказавший слой, проверить полномочия и Origin |
| 404 | Сопоставить путь до и после проксирования |
| 400 или 426 | Посмотреть требования обработчика к запросу и протоколу |
| 502 или 504 | Разобрать обращение прокси к upstream по его журналу |
Не добавляйте в ответ строку «101» вручную. Переключение должно быть результатом корректного согласования клиента и сервера, после которого оба действительно обрабатывают WebSocket. Подмена статуса обычной HTML-страницы не создаёт такой канал.
Проверьте также, не смешаны ли разные протоколы уровня приложения. Наличие слова WebSocket в настройках библиотеки не означает, что произвольный клиент понимает её обмен. Сначала установите, какой клиент, сервер и подпротокол должны работать вместе, и воспроизведите именно этот вариант.
Отделите простой соединения от общего времени работы
Для proxy_read_timeout Nginx документирует интервал между последовательными чтениями данных от upstream. Это не общий предел существования ответа. По умолчанию указан интервал 60 секунд, но в проекте может действовать другое значение из выбранного контекста. Если сервер молчит дольше разрешённого, соединение закрывается.
Измерьте паузу от последнего сообщения сервера, а не только время от открытия страницы. Обрыв примерно через минуту после последнего обмена поддерживает гипотезу тайм-аута, но не доказывает её. Сопоставьте момент с журналом, эффективной настройкой и ограничениями других прокси. Независимый предел у внешнего узла может оказаться короче.
Исходящие сообщения браузера сами по себе не гарантируют сброс ожидания чтения от backend. Нужны данные, которые действительно идут обратно через рассматриваемый участок. Если клиент отправляет запросы heartbeat, а приложение на них не отвечает, увеличение их частоты не подтверждает работоспособность обратного направления.
Проверьте, является ли ограничение паузой без обмена или максимальной длительностью канала. Во втором случае регулярные сообщения могут не предотвратить закрытие. Решение выбирают по фактическому правилу: согласованный heartbeat, изменение допустимого ожидания либо предусмотренное переподключение. Бесконечный тайм-аут не заменяет обнаружение зависшего приложения.
Различайте Ping/Pong протокола и сообщения приложения
В WebSocket есть управляющие кадры Ping и Pong. Стандарт браузерного WebSocket API отмечает, что они напрямую не представлены в JavaScript API. Отправка строки "ping" методом send создаёт обычное сообщение приложения; сервер должен отдельно понимать такой формат и отвечать на него.
Выберите предусмотренный библиотекой механизм. Серверная библиотека может отправлять управляющие кадры и контролировать ответы. Прикладной heartbeat может содержать идентификатор запроса и ожидаемый ответ. Эти варианты не следует смешивать в журнале: иначе появление любого сообщения назовут подтверждением связи, хотя ответ на конкретную проверку не получен.
Интервал и предел ожидания выбирайте с запасом относительно ограничений всей цепочки и измеренных задержек. Например, сообщения каждые 20 секунд при допустимой паузе 75 секунд могут служить учебной настройкой, но не универсальным рецептом. Проверьте задержку выполнения, перегрузку и поведение фоновой вкладки; браузерный таймер не является точными часами доступности сервера.
Успешный heartbeat подтверждает ограниченный обмен. Он не доказывает, что оформлена подписка на заказ или работает источник его статусов. Поэтому после проверки живого канала нужен отдельный тест полезного сообщения с ожидаемым содержимым.
Если канал открыт, проследите подписку и источник событий
При действующем соединении проверьте следующий шаг протокола приложения. Отправлен ли запрос подписки, принят ли он и подтверждено ли право получать нужные данные? Сообщение «подключено» может появиться раньше подписки. Проверьте комнату, пользователя или объект, на который подписался клиент, без публикации их приватного содержимого.
Создайте контролируемое событие и проследите его путь. Оно должно возникнуть в источнике, попасть к обслуживающему соединение процессу, быть отправлено и обработано интерфейсом. Если кадр виден в Messages, но экран не обновился, проблема может находиться в разборе данных или состоянии клиента. Изменение Upgrade на этом этапе уже не объясняет наблюдение.
При нескольких экземплярах приложения проверьте, как события доходят до каждого нужного соединения. Сокет может жить на одном процессе, а изменение заказа произойти на другом. Закрепление клиента за сервером само по себе не создаёт общий канал событий. Команда должна подтвердить механизм доставки между процессами и восстановление подписок после перезапуска.
Отдельно измеряйте задержку полезных сообщений. Открытый канал, который выдаёт устаревшие данные, не решает задачу пользователя. Заранее определите допустимую задержку, источник времени и способ отличать новое состояние от уже полученного.
Подготовьте переподключение и восстановление состояния
Мобильная сеть, смена маршрута или перезапуск приложения могут разорвать соединение даже при верной конфигурации. Повторное подключение организует код приложения либо выбранная библиотека. Оно должно учитывать причину отказа, состояние входа и допустимую частоту попыток, а не создавать бесконечный быстрый цикл.
Используйте ограничение повторов и задержку с разбросом, чтобы множество клиентов не вернулось одновременно после аварии. При истёкшей сессии сначала восстановите предусмотренную авторизацию. Показывайте посетителю состояние связи и актуальность данных: старый статус заказа не должен выглядеть свежим только потому, что HTML остаётся на экране.
После нового соединения повторно подтвердите подписку и восстановите пропущенные изменения по договору приложения. Возможные подходы — получение актуального снимка или запрос истории после подтверждённого идентификатора. Сам WebSocket не создаёт историю сообщений за время отсутствия клиента.
Для исходящих операций различайте отправку в транспорт и подтверждённый результат. Если ответ потерян, действие могло выполниться. Перед повтором нужны идентификатор операции, сверка состояния и предусмотренная защита от дублей. Повторная отправка всех прежних сообщений без разбора может повторить изменение. Проверка переподключения должна включать этот неопределённый исход.
Учебный разбор: чат отключается при отсутствии ответов
Представим вымышленный чат с HTTP/1.1 WebSocket через один Nginx. Соединение получило 101, приложение отправило приветствие, а фактический proxy_read_timeout равен 75 секундам. Во время эксперимента нет других прокси с более коротким ограничением. Клиент посылает прикладные сообщения heartbeat, но обработчик сервера на них не отвечает.
| Относительное время | Наблюдение | Значение для проверки |
|---|---|---|
| 0 секунд | Получено приветствие сервера | Есть подтверждённые входящие данные |
| 20, 40 и 60 секунд | Клиент отправляет heartbeat, ответа нет | Обратное направление остаётся без данных |
| Около 75 секунд | Канал закрывается, клиент фиксирует 1006 | Нужно сопоставить паузу с журналом Nginx |
Команда проверяет журнал и подтверждает срабатывание ожидания чтения от upstream. Исправление состоит в реализации согласованного ответа сервера и проверке его прохождения. Простое учащение исходящих сообщений клиента не устраняло отсутствие обратных данных. Произвольное увеличение ожидания лишь отложило бы проявление той же причины.
В повторном опыте проверяют обмен во время паузы полезных событий, затем намеренный разрыв сети и восстановление подписки. Отдельно создают тестовое сообщение чата и подтверждают его появление. Эти проверки показывают разные результаты: живой канал, работающий возврат и доставку нужного события.
Как принять исправление и продолжить наблюдение
В задаче на исправление сохраните версию конфигурации, конкретный маршрут и установленную причину. Подрядчик должен показать наблюдение до изменения и результат того же сценария после него. «Повысили тайм-аут» или «добавили Upgrade» описывает действие, но ещё не подтверждает восстановление функции.
- Соединение устанавливается из нужной страницы с предусмотренными полномочиями.
- Правильно согласованы транспорт и подпротокол приложения.
- Подписка подтверждена, контролируемое событие получено и отображено.
- Проверены обмен в период молчания, отключение сети и возвращение клиента.
- После повторного подключения восстановлено состояние, значимые операции не продублированы.
- Проверены несколько экземпляров приложения и используемые пользователями устройства.
Проводите итоговую проверку через рабочую цепочку доставки, а не только рядом с backend. Зафиксируйте ограничения наблюдения: выбранный браузер, длительность, сценарий и нагрузку. Один короткий успешный опыт не подтверждает все длительные сессии. Повтор после выпуска должен также показать, что новая конфигурация действует на нужных узлах.
Подключите SiteGuard для внешнего HTTP-контроля доступности сайта. Рукопожатие WebSocket, устойчивость канала и доставку сообщений проверяйте отдельным сценарием команды.