Короткий ответ: WebSocket на PHP делают не как обычный PHP-файл, который запускается на каждый запрос, а как долгоживущий процесс. Он постоянно слушает порт, принимает подключения и рассылает сообщения. Для этого используют библиотеки или runtimes: Ratchet, Swoole, RoadRunner, ReactPHP-стек и похожие решения
Почему обычный PHP-подход не подходит
Классический PHP под Apache/PHP-FPM живет в модели:
HTTP request -> PHP обработал -> response -> процесс свободен
WebSocket требует другого:
connect -> keep connection open -> messages -> close
Поэтому WebSocket-сервер на PHP должен быть отдельным процессом, который вы запускаете через CLI, supervisor или systemd
Минимальная схема
Browser -> Nginx -> PHP WebSocket process
Nginx принимает wss://, прокидывает Upgrade-заголовки, а PHP-процесс слушает внутренний порт
Примерный стек
Для учебного проекта можно посмотреть Ratchet, но для нового продакшена важно проверить актуальность библиотеки и совместимость с вашей версией PHP
Общая идея:
composer require cboden/ratchet
Затем создается класс обработчика сообщений и запускается сервер из CLI. Для серьезного проекта дополнительно нужны логирование, контроль памяти, reconnect-логика клиентов, авторизация и supervisor/systemd
Что хранить в PHP WebSocket сервере
Сервер может хранить список подключенных клиентов:
private SplObjectStorage $clients;
При подключении клиент добавляется, при отключении удаляется, при сообщении сервер рассылает данные нужным клиентам
Но не храните важное состояние только в памяти WebSocket-процесса. После перезапуска оно исчезнет. Пользователи, комнаты, сообщения и права должны жить в базе или другом устойчивом хранилище
Авторизация клиента
PHP WebSocket сервер должен понимать, кто подключился. Один из вариантов — передать короткоживущий токен при подключении или первым сообщением:
{
"type": "auth",
"token": "temporary-token"
}
После проверки сервер связывает соединение с user id. Не стоит доверять только параметру userId из клиента: его легко подменить. Проверяйте токен на стороне сервера и не отправляйте пользователю чужие события
Масштабирование
Один PHP WebSocket процесс может обслуживать ограниченное количество клиентов. Если процессов несколько, им нужен общий брокер: Redis, очередь сообщений или другой pub/sub. Тогда событие из backend-приложения попадет всем нужным WebSocket-процессам
Как связать с обычным PHP-приложением
Обычное приложение может писать событие в Redis или базу, а WebSocket-процесс читать и рассылать его клиентам:
Laravel/Symfony controller -> Redis pub/sub -> PHP WebSocket process -> Browser
Так HTTP-часть не должна знать о конкретных соединениях. Она публикует событие, а долгоживущий процесс доставляет его тем, кто сейчас подключен
Nginx proxy
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;
}
Для публичного сайта добавьте TLS и используйте wss://
Частые ошибки
Первая ошибка — пытаться сделать WebSocket внутри обычного контроллера Laravel/Symfony без отдельного процесса
Вторая ошибка — запускать сервер в SSH-сессии и закрывать терминал. Используйте systemd или supervisor
Третья ошибка — не удалять отключенных клиентов из коллекции
Четвертая ошибка — не проверять совместимость библиотеки с текущей версией PHP
Пятая ошибка — отдавать WebSocket наружу без wss и авторизации
Шестая ошибка — смешивать PHP-FPM и WebSocket-процесс как одно и то же. Это разные процессы с разным жизненным циклом
Самопроверка
Поднимите PHP WebSocket сервер на локальном порту, подключитесь из браузера, отправьте ping, получите ответ. Затем перезапустите процесс и убедитесь, что он снова принимает подключения через supervisor/systemd
Что почитать дальше по WebSocket
Если нужен общий маршрут по теме, откройте рубрику WebSocket. Для соседних задач пригодятся эти разборы:
- Django: как подружить Celery с WebSocket
- Django: как сохранить биржевые данные, полученные по WebSocket
- FastAPI WebSocket: уведомления в браузер
- Fedora 43: как установить WebSocket сервер



