WebSocket держит одно TCP-соединение открытым длительное время и обменивается кадрами в обе стороны без повторных HTTP-запросов — для чата, live-уведомлений или админ-панели с обновлением в реальном времени. Nginx как обратный проксi не поддерживает Upgrade-соединения без явной настройки: без неё запрос на WebSocket просто не пройдёт дальше обычного HTTP-прокси.
Почему WebSocket не работает через proxy_pass «из коробки»
Обычный proxy_pass в nginx настроен на цикл HTTP-запрос-ответ, а WebSocket начинается как HTTP-запрос с заголовком Upgrade: websocket, после которого соединение должно перейти в режим двустороннего обмена кадрами. Nginx по умолчанию не прокидывает заголовок Upgrade на бэкенд и держит соединение по правилам обычного HTTP-proxy, что ломает переход на протокол WebSocket.
Базовая настройка location и proxy_pass для сайта на VDS не меняется и описана в статье про настройку Nginx. Для WebSocket к ней добавляются всего несколько директив, но без них соединение рвётся сразу после handshake.
Настройка map и заголовков Upgrade/Connection
Рекомендованная в документации nginx схема — объявить map, который превращает значение заголовка Upgrade в значение заголовка Connection: если Upgrade присутствует, Connection становится upgrade, а если заголовка нет (обычный HTTP-запрос к тому же location), Connection закрывается явно.
Сам location получает proxy_http_version 1.1 — HTTP/1.0 не поддерживает Upgrade, — и оба заголовка прокидываются на бэкенд через proxy_set_header.
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_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
Таймауты: почему соединение рвётся через минуту
Здесь важны три директивы, и их значения по умолчанию в nginx слишком малы для долгоживущего соединения.
| Директива | Значение по умолчанию | Зачем менять для WebSocket |
|---|---|---|
| proxy_read_timeout | 60 секунд | разрывает соединение, если от бэкенда нет данных дольше этого времени |
| proxy_send_timeout | 60 секунд | то же самое, но для отправки данных на бэкенд |
| proxy_connect_timeout | 60 секунд | на долгоживущее соединение влияет меньше, но тоже стоит проверить |
Если после увеличения таймаутов соединение всё равно обрывается с кодом 502 или 504, причину стоит искать по статье про диагностику 502 и 504 — чаще всего дело не в таймауте, а в самом бэкенде.
WebSocket-сервер на стороне приложения
Сам WebSocket-сервер, который принимает соединение после nginx, — отдельное приложение, а не часть PHP-FPM: обычный php-fpm обрабатывает один запрос и завершает процесс, что не подходит для долгоживущего соединения.
Пример такого сервера на PHP с библиотекой Ratchet разобран в статье про WebSockets на PHP с Ratchet. Nginx в этой схеме проксирует соединение на порт, который слушает именно этот процесс, а не php-fpm.
Чек-лист перед запуском WebSocket за nginx
Перед тем как выводить WebSocket-соединение в продакшен через nginx, стоит проверить следующее.
- proxy_http_version установлен в 1.1, иначе Upgrade не пройдёт даже с правильными заголовками.
- map для Connection учитывает оба случая — с Upgrade и без него, для location, который обслуживает и обычные HTTP-запросы, и WebSocket.
- proxy_read_timeout и proxy_send_timeout увеличены настолько, чтобы не рвать соединения чата или live-обновлений раньше времени.
- На стороне приложения реализован ping/pong или другой механизм проверки живого соединения, а не только надежда на таймауты nginx.
- Балансировка между несколькими WebSocket-процессами, если она есть, учитывает липкость сессии — клиент не должен прыгать между процессами посреди соединения.
Частые вопросы
Нужен ли отдельный server-блок для WebSocket?
Нет, обычно достаточно отдельного location в том же server-блоке, что обслуживает основной сайт — например /ws/. Полноценный HTTP-сайт и WebSocket-эндпоинт спокойно живут в одном конфиге, если заголовки Upgrade и Connection настроены именно для нужного location.
Что если за nginx стоит ещё и балансировщик нагрузки?
Балансировщик перед nginx должен сам поддерживать проксирование Upgrade-соединений и не обрывать долгоживущие TCP-сессии по собственным таймаутам — иначе настройка на уровне nginx не поможет, проблема окажется на уровень выше.
Как проверить, что WebSocket действительно работает через nginx?
Проще всего — через curl с заголовками, имитирующими handshake, или через браузерную консоль разработчика, где виден статус соединения и обмен кадрами. Код ответа 101 Switching Protocols в логах nginx подтверждает, что переход на WebSocket прошёл успешно.