renoki-co/clusteer
Clusteer is a Laravel package for running Laravel Echo Server in a clustered setup, letting you scale WebSocket broadcasting across multiple nodes with shared Redis and a load balancer while simplifying configuration for horizontally scaled real-time apps.
Prerequisites (3.3.0): PHP 8.0+ and Laravel 8.x or 9.x. This release adds Laravel 9 support but drops PHP 7.3/7.4 and Laravel 7.x — check your composer.json platform constraints before upgrading.
composer require renoki-co/clusteer
php artisan vendor:publish --tag=clusteer.config
In config/clusteer.php set apps[0].id, key, secret, plus optional capacity, enable_client_messages, enable_statistics, and the Redis connection to use for pub/sub.clusteer connection in config/broadcasting.php (app id/key/secret + options.host, options.port, options.scheme), or point the existing pusher driver at the clusteer host, then set BROADCAST_DRIVER=clusteer and clear cached config.new Echo({
broadcaster: 'pusher',
key: import.meta.env.VITE_CLUSTEER_APP_KEY,
wsHost: import.meta.env.VITE_CLUSTEER_HOST,
wsPort: 6001,
forceTLS: false,
disableStats: true,
enabledTransports: ['ws', 'wss'],
});
php artisan clusteer:start --debug
Subscribe to a public channel in the browser, then fire broadcast(new TestEvent()) from php artisan tinker.Where to look first: config/clusteer.php (all knobs), src/ClusteerServiceProvider.php (driver registration), the Clusteer broadcaster class (HTTP payload shape), the clusteer:start command (host/port/debug options), and routes/channels.php for private/presence authorization.
First real use case: replacing a hosted Pusher/Ably dependency for live notifications, chat, or dashboards when you already run multiple app servers and want the WebSocket tier to scale horizontally over your own Redis.
Multi-node topology. Run one clusteer:start process per app node, all pointed at the same Redis instance/database. The Laravel broadcaster POSTs an event to the configured host (one node), that node publishes to Redis, and every node fans the message out to its own connected sockets. Put the broadcaster's host behind a load balancer, or point each node at its own local process to avoid a single entry point.
Process supervision. Keep the server alive with Supervisor/systemd (autorestart=true, capture stdout/stderr). Treat it like a queue worker: it is long-running and holds old code, so restart it on every deploy.
Channel authorization. Use standard Broadcast::channel() callbacks in routes/channels.php. Private and presence channels hit Laravel's /broadcasting/auth endpoint, so normal guards/session auth apply — including presence member lists.
Client events ("whisper"). Enable enable_client_messages per app in config/clusteer.php if clients should emit directly; leave it off for server-authoritative feeds.
Local vs production. In development, run a single node and toggle BROADCAST_DRIVER=log when you don't need sockets. In production, keep --debug only while diagnosing; it is noisy.
Testing. Assert broadcasts with Event::fake() / Broadcast::fake() in feature tests; the WebSocket server isn't needed for those. Reserve live-server checks for a small integration suite that subscribes and fires a real event.
TLS. Terminate TLS at nginx/HAProxy (proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";) and forward to the clusteer port. Set the broadcaster/Echo scheme/forceTLS to match so generated auth URLs and the client handshake stay consistent.
config/clusteer.php, the broadcasting connection, and the Echo client. A mismatch shows up as a 403 on handshake or on /broadcasting/auth.php artisan config:cache freezes clusteer config. Re-run it on deploy after changing apps or Redis settings.redis-cli monitor while firing an event.auth headers; 403 = channel callback denied or wrong guard; cross-domain cookies require correct session domain and Broadcast::routes(['middleware' => ['web']]).wss (forceTLS: true); otherwise the browser silently blocks the connection.disableStats: true in Echo unless you've enabled statistics for that app.redis-cli ping; confirm the process is listening (ss -lntp); run with --debug; inspect the handshake and the /apps/{id}/events POST in the browser/network tab; then confirm the event reached Redis with redis-cli monitor.How can I help you explore Laravel packages today?