Симптом: чат работает, потом через минуту-две сообщения перестают доходить, а клиент об этом не знает. Перезагрузка страницы помогает. В логах — тишина.
Это почти всегда не WebSocket, а обратный прокси, который закрывает «простаивающее» соединение.
Минимальный правильный блок
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 "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
Четыре вещи здесь важны все:
proxy_http_version 1.1— без негоUpgradeне пройдёт, и вы получите бесконечный 101-цикл или тихий отказ.proxy_set_header Upgrade/Connection— собственно переключение протокола.proxy_read_timeout— дефолт 60 секунд. Если сервер не отправляет ничего минуту, nginx прибьёт соединение. Для чата это ровно та минута, через которую «всё сломалось».proxy_buffering off— иначе nginx копит ответы сервера и выдаёт их пачками.
Почему keepalive не спасает
Клиенты и серверы часто шлют ping каждые 30 секунд именно чтобы обойти таймаут. Это работает, но создаёт другую проблему: если ping не проходит, вы узнаёте об этом только по отсутствию pong — то есть должны сами считать время. В приложении обязана быть проверка: если последний pong старше двух интервалов, соединение пересоздаётся.
Как проверить, что дело в прокси
timeout 120 curl -sN -o /dev/null -w '%{http_code} %{time_total}\n' \
-H 'Connection: Upgrade' -H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
http://127.0.0.1:8080/ws
Если curl висит ровно 60 секунд и выходит — таймаут прокси. Если рвётся сразу — дело в бэкенде.
Ещё один слой, о котором забывают: балансировщики и облачные LB поверх nginx держат свои таймауты, и самый маленький из них и определяет реальное время жизни соединения. Ставить 3600s в nginx бессмысленно, если перед ним стоит LB с лимитом 30 секунд.