Skip to main content

WebSockets in PHP with Ratchet: Chat and Notifications

PHP · 19.04.2026 · 4 min read
Illustration for “WebSockets in PHP with Ratchet: Chat and Notifications”

WebSockets in PHP with Ratchet

WebSockets let you create two-way connections between the server and the browser without constant HTTP requests. Ratchet is a popular PHP library built on top of ReactPHP for building WebSocket servers.

Installing Ratchet

composer require cboden/ratchet

A simple WebSocket server

<?php
require 'vendor/autoload.php';

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;

class Chat implements MessageComponentInterface {
    protected $clients;

    public function __construct() {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn) {
        $this->clients->attach($conn);
        echo "New connection: {$conn->resourceId}\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        foreach ($this->clients as $client) {
            if ($client !== $from) {
                $client->send($msg);
            }
        }
    }

    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        $conn->close();
    }
}

$server = IoServer::factory(
    new HttpServer(new WsServer(new Chat())),
    8080
);
$server->run();

Client-side JavaScript

const ws = new WebSocket('wss://yourdomain.com/ws');

ws.onopen = () => console.log('Connected');
ws.onmessage = (e) => console.log('Message:', e.data);

// Sending a message
ws.send(JSON.stringify({ user: 'Alice', text: 'Hello!' }));

Nginx reverse proxy for WebSockets

server {
    listen 443 ssl;
    server_name yourdomain.com;

    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_read_timeout 86400;
    }
}

Autostart via systemd

[Unit]
Description=Ratchet WebSocket Server
After=network.target

[Service]
Type=simple
User=www-data
ExecStart=/usr/bin/php /var/www/html/ws-server.php
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
systemctl enable ratchet-ws
systemctl start ratchet-ws
systemctl status ratchet-ws
Alternative: For high-load systems, consider using Swoole or ReactPHP directly — they outperform Ratchet and support coroutines.

Real WebSocket Server Errors

Port already in use

PHP Fatal error:  Uncaught React\\Socket\\ConnectionException: Could not bind to tcp://0.0.0.0:8080: Address already in use in /var/www/html/vendor/react/socket/src/TcpServer.php:54

A previous server process did not shut down cleanly (it hung after a crash, or was started manually and forgotten) and is still holding the port. Check whether the port is free before starting, rather than relying on systemd alone.

A WebSocket handshake error in the browser

WebSocket connection to 'wss://yourdomain.com/ws' failed: Error during WebSocket handshake: Unexpected response code: 502

A 502 at the handshake stage almost always means Nginx could not reach the Ratchet PHP process: either the server is not running, or proxy_pass points to the wrong port. A 400 at the same stage usually means the Upgrade and Connection headers are missing from the reverse proxy config.

When Ratchet Does Not Fit

  • Single-threaded event loop: one synchronous call — a slow SQL query or file_get_contents() to an external API — blocks processing for every connected client until it finishes.
  • No built-in clustering: spreading load across several servers requires an external message broker (Redis Pub/Sub) and sticky sessions on the load balancer — Ratchet itself does not handle this.
  • CPU-heavy processing of each message (encryption, image generation) does not fit the single-threaded model — that work should be offloaded to separate workers through a queue, for example on Swoole, which has built-in coroutines.

Verifying the Result

# Is the port actually being listened on by the server process
ss -tlnp | grep 8080

# A manual handshake check without a browser
curl -i -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  http://127.0.0.1:8080/

# systemd unit status and the latest log lines
systemctl status ratchet-ws --no-pager
journalctl -u ratchet-ws -n 50

A successful response to the manual curl request is a 101 Switching Protocols status. Any other code means there is a problem before the chat logic even runs — check it separately from Ratchet's own code.

It helps to log the number of active connections inside onOpen and onClose — a sharp rise in client count with no matching rise in site traffic usually means old connections are not closing properly and are piling up, gradually exhausting the process's memory.

Was this article helpful?
← Back to Knowledge Base Ask Support