В реальных веб‑проектах WebSocket чаще всего прячут за обратным прокси — и именно Nginx остается стандартом де‑факто. На VDS такой дизайн дает контроль над SSL‑терминацией, нагрузкой и безопасностью, не усложняя приложение. Ниже — практическое руководство: рабочие конфиги, нюансы заголовков Upgrade/Connection, таймауты для долгоживущих соединений и типовые ошибки. Если вы как раз выбираете сервер, обратите внимание на VDS.
Как работает WebSocket за Nginx
WebSocket начинается с HTTP‑рукопожатия: клиент отправляет запрос с Upgrade: websocket и Connection: Upgrade, а сервер отвечает 101 Switching Protocols. После этого канал становится двунаправленным, без дальнейших HTTP‑заголовков. Задача Nginx — корректно пробросить это рукопожатие к бэкенду и не разорвать длительное соединение преждевременно.
Ключевые моменты в конфигурации:
proxy_http_version 1.1— для рукопожатия WebSocket;- проброс заголовков
UpgradeиConnectionс учетом их отсутствия; - отключение буферизации для двунаправленного канала;
- увеличенные таймауты чтения;
- правильная SSL‑терминация (если клиент подключается по
wss://).
Базовая схема и минимальный конфиг
Предположим, приложение слушает 127.0.0.1:3000, а клиенты будут подключаться к /ws/. Начнем с блока http и map для безопасного формирования заголовка Connection:
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream ws_backend {
server 127.0.0.1:3000;
keepalive 64;
}
server {
listen 80;
server_name example.com;
location /ws/ {
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_pass http://ws_backend;
proxy_read_timeout 1h;
proxy_send_timeout 1m;
proxy_connect_timeout 5s;
proxy_buffering off;
proxy_request_buffering off;
}
}
}
Комментарии:
mapподставляетConnection: upgrade, только если клиент прислалUpgrade. Иначе —close, что предотвращает ложные апгрейды.proxy_http_version 1.1обязателен, иначе WebSocket‑апгрейд не произойдет.proxy_buffering offисключает буферизацию данных — для двунаправленного канала это критично.proxy_read_timeoutувеличен до часа: без трафика соединение не падает. Конкретное значение выбирайте под вашу модель трафика и пинги приложения.
SSL‑терминация и WSS
В типичной схеме Nginx завершает TLS на 443 и проксирует незашифрованным каналом к локальному бэкенду. Клиент подключается по wss://, а бэкенд по ws:// (через proxy_pass http://...). Это упрощает управление сертификатами и шифросетами, а также разгружает приложение. Для выпуска и продления удобно использовать SSL‑сертификаты.
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/example.crt;
ssl_certificate_key /etc/ssl/example.key;
# Важно: WebSocket рукопожатие пойдет по HTTP/1.1 даже при включенном http2
location /ws/ {
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_pass http://ws_backend;
proxy_read_timeout 1h;
proxy_send_timeout 1m;
proxy_connect_timeout 5s;
proxy_buffering off;
proxy_request_buffering off;
}
}
Даже с listen ... http2; браузеры по‑прежнему выполняют WebSocket‑апгрейд как HTTP/1.1 — стороне Nginx это не мешает. Для перехода всего сайта на HTTPS с редиректом и HSTS может пригодиться разбор: миграция на HTTPS, 301 и HSTS.

Таймауты и keepalive: что влияет на обрыв
WebSocket‑соединения долгоживущие; стандартные таймауты Nginx часто слишком малы. Сфокусируемся на ключевых директивах и системных настройках.
proxy_read_timeout— сколько Nginx будет ждать данных от бэкенда, не закрывая соединение. Для «тихих» соединений ставят минуты или часы; если приложение шлет пинги, можно короче.proxy_send_timeout— таймаут отправки в бэкенд. Обычно менее критичен для WS, но полезен при сетевых проблемах.proxy_connect_timeout— время установления TCP‑соединения к бэкенду. При превышении клиент быстро узнает об ошибке и переподключится.keepaliveвupstream— число держимых открытых TCP‑соединений к бэкендам для повторного использования (не путать с WebSocket).keepalive_timeoutв контекстеhttp— влияет на клиентские HTTP keep‑alive до апгрейда; к уже апгрейженному каналу не относится напрямую.
Проверяйте таймауты по всей цепочке: CDN, балансировщик, Nginx, приложение, БД и очереди. Самый короткий из них оборвет соединение.
- Если приложение отправляет пинги (WebSocket ping/pong) каждые 30–60 секунд —
proxy_read_timeout 2mдостаточно. Если нет — поднимайте до 10–60 минут. - На уровне ОС полезно согласовать TCP‑keepalive с пингами приложения.
- Следите, чтобы таймауты БД/брокеров сообщений не были короче ожиданий в приложении.
Логи и диагностика: видим 101, 502, 504
Для разбора инцидентов удобен отдельный формат логов с кодами апстрима и заголовками апгрейда:
log_format ws '$remote_addr - $host $request $status $upstream_status '
'ua=$http_user_agent upg=$http_upgrade conn=$http_connection '
'rt=$request_time urt=$upstream_response_time';
access_log /var/log/nginx/access_ws.log ws;
На что смотреть:
101в$statusи$upstream_status— рукопожатие ок.502— бэкенд недоступен или упал; проверьтеproxy_pass, firewall,upstream, логи приложения.504— таймаут ожидания от бэкенда; увеличьтеproxy_read_timeoutи убедитесь, что приложение держит канал живым.400— часто ошибка в заголовкахUpgrade/Connectionили несовместимый путь.
Масштабирование: несколько бэкендов и привязка сессий
WebSocket‑соединение «липкое»: одна сессия живет на одном инстансе. Для масштаба добавляют несколько серверов в upstream. Если нужно закрепление клиента за узлом (состояние в памяти), подойдет IP‑привязка:
upstream ws_backend {
ip_hash;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
keepalive 128;
}
У ip_hash есть нюансы: неравномерность распределения и чувствительность к NAT. Альтернатива — перенос состояния в Redis/БД и отказ от липкости на уровне прокси. Не забудьте лимиты дескрипторов и worker_connections — WebSocket множит число одновременных соединений.

Частые ошибки и их исправление
1) Неверные заголовки Upgrade/Connection
Симптом: соединение не апгрейдится, в логах 400. Решение: используйте map, как в примере, и проверьте proxy_http_version 1.1. Значения должны быть ровно Upgrade: websocket и Connection: upgrade.
2) Смешанный контент и переход на WSS
Если сайт открыт по HTTPS, браузер заблокирует ws://. Используйте wss:// и корректный сертификат. На стороне Nginx — SSL‑терминация, как в примере для 443.
3) 502/504 при простое
Классика для «немых» соединений: бэкенд или Nginx закрывают канал. Поднимайте proxy_read_timeout и реализуйте heartbeat. Проверьте сетевые idle‑политики.
4) Буферизация сломала поток
Забыли proxy_buffering off — двунаправленный обмен начнет «залипать». Выключайте буферизацию и для запросов (proxy_request_buffering off).
5) HTTP/2 и WebSocket
http2 на 443 — не проблема: браузер делает апгрейд по HTTP/1.1. Главное — proxy_http_version 1.1 к бэкенду. Не путайте это с SSE — у них иная модель.
Безопасность: минимум, который стоит включить
- Проверяйте
Originна бэкенде. WebSocket не делает CORS как XHR/fetch. - Разделяйте пути: держите WS в отдельном
location(/ws/), ограничивайте методы/заголовки. - Ограничения по ресурсам: ulimit,
worker_rlimit_nofile,worker_connectionsпод вашу нагрузку. - Логи и ротация: отдельный
access_logдля WS и агрессивная ротация. - Изоляция бэкенда: слушайте только
127.0.0.1или внутренний интерфейс.
Пример end‑to‑end: Node.js ws за Nginx
Минимальный сервер на Node.js (библиотека ws):
// app.js
const WebSocket = require('ws');
const server = new WebSocket.Server({ host: '127.0.0.1', port: 3000 });
server.on('connection', socket => {
socket.send('hello');
socket.on('message', msg => {
socket.send('echo: ' + msg);
});
});
console.log('WS on 127.0.0.1:3000');
Nginx‑конфиг — как в начале статьи: location /ws/ с proxy_http_version 1.1, заголовками Upgrade/Connection и отключенной буферизацией. Проверка из консоли браузера:
const ws = new WebSocket('wss://example.com/ws/');
ws.onmessage = e => console.log(e.data);
ws.onopen = () => ws.send('ping');
Если вместо wss:// используете ws:// на 80‑м порту и в браузере открыт HTTPS‑сайт — подключение будет заблокировано политикой смешанного контента.
Чеклист перед продакшеном
- Рукопожатие 101 проходит: в логах видно
status=101,upstream_status=101. - Заголовки
Upgrade/Connectionпрокинуты верно черезmap. - Включена SSL‑терминация, редирект с 80 на 443, сертификат актуален.
- Выключена буферизация, увеличен
proxy_read_timeout. - Лимиты файловых дескрипторов и
worker_connectionsсоответствуют ожидаемой нагрузке. - Логи вынесены в отдельный файл, настроена ротация.
- Бэкенд слушает только локальный интерфейс, firewall закрыт для внешнего доступа к нему.
Итоги
Чтобы WebSocket стабильно работал за Nginx на VDS, достаточно нескольких точных настроек: HTTP/1.1 к апстриму, корректные Upgrade/Connection, отключенная буферизация и растянутые таймауты. SSL‑терминация на Nginx упрощает сертификаты (SSL), а логирование помогает оперативно ловить 101/502/504. Масштабирование через upstream и дисциплина системных лимитов позволяют выдерживать тысячи постоянных коннектов.


