WebSocket тримає одне TCP-з'єднання відкритим тривалий час і обмінюється кадрами в обидва боки без повторних HTTP-запитів — для чату, live-сповіщень чи адмін-панелі з оновленням у реальному часі. Nginx як зворотний проксі не підтримує Upgrade-з'єднання без явного налаштування: без нього запит на WebSocket просто не пройде далі звичного HTTP-проксі.
Чому WebSocket не працює через proxy_pass «з коробки»
Звичний proxy_pass у nginx налаштований на цикл HTTP-запит-відповідь, а WebSocket починається як HTTP-запит із заголовком Upgrade: websocket, після якого з'єднання повинне перейти у режим двостороннього обміну кадрами. Nginx за умовчанням не передає заголовок Upgrade на бекенд і тримає з'єднання за правилами звичного HTTP-проксі, що ламає перехід на протокол 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 пройшов успішно.