Why a Single Reverb Node Breaks Under Load
Laravel Reverb is a first-party WebSocket server that integrates tightly with Laravel's broadcasting system. A single node works perfectly in development and handles modest traffic, but the moment you add a second application server — or run Reverb behind a load balancer — you hit a fundamental problem: WebSocket connections are stateful and sticky to one process.
Client A connects to node 1. Client B connects to node 2. When your application broadcasts an event, only the node that received the HTTP request knows about it. Without a shared message bus, half your users miss the event.
The Redis Pub/Sub Bridge
Reverb ships with a Redis scaling driver that solves this with a publish/subscribe bus. Every Reverb node subscribes to the same Redis channel. When any node receives a broadcast, it publishes to Redis; every other node picks it up and pushes it to its own connected clients.
Enable it in config/reverb.php:
'scaling' => [
'driver' => 'redis',
'connection' => env('REVERB_SCALING_CONNECTION', 'default'),
],
Then make sure your redis connection in config/database.php points at the same Redis instance (or cluster) that all Reverb nodes share. A dedicated Redis database index keeps Reverb traffic isolated:
'reverb_scaling' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REVERB_REDIS_DB', '1'),
],
Reference it: REVERB_SCALING_CONNECTION=reverb_scaling.
Presence Channel State Across Nodes
Presence channels track who is online. In a single-node setup, that state lives in memory. Across nodes, each node only knows about its own connections — so channel:here events become inconsistent.
Reverb stores presence membership in Redis when the scaling driver is active. The key pattern is reverb:presence:{channel}. You should never query this key directly; instead rely on the here, joining, and leaving client events. What you do need to ensure is that your Redis instance has enough memory and that you set a sensible TTL policy — presence keys are cleaned up on disconnect, but a crashed node can leave orphaned entries until the key expires.
Force a short key TTL in your Reverb config:
'presence' => [
'timeout' => 120, // seconds before a stale member is evicted
],
Load Balancer Configuration
WebSocket connections require HTTP Upgrade. Most load balancers need explicit configuration:
upstream reverb {
least_conn;
server reverb1:8080;
server reverb2:8080;
}
server {
listen 443 ssl;
location /app/ {
proxy_pass http://reverb;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
}
Note least_conn rather than round-robin. WebSocket connections are long-lived, so round-robin will pile connections onto whichever node happened to be first. least_conn distributes active connections more evenly.
Supervisor and Process Management
Each Reverb node should run under Supervisor with restart-on-failure:
[program:reverb]
command=php /var/www/artisan reverb:start --host=0.0.0.0 --port=8080
autostart=true
autorestart=true
stopwaitsecs=10
stdout_logfile=/var/log/reverb.log
Set stopwaitsecs high enough for in-flight connections to drain gracefully. Reverb sends a close frame to clients on SIGTERM; clients with reconnect logic will re-connect to another node transparently.
Health Checks and Observability
Reverb exposes a /apps/{appId}/channels REST endpoint (authenticated with your app secret) that returns active channel counts. Wire this into your uptime monitor or Prometheus scraper to alert on node divergence — if two nodes report wildly different channel counts, your Redis pub/sub bridge may have silently failed.
Key Takeaways
- Enable the Redis scaling driver so all Reverb nodes share a pub/sub bus.
- Use a dedicated Redis database index to isolate Reverb traffic.
- Presence channel state is stored in Redis automatically; set a
presence.timeoutto evict stale members from crashed nodes. - Configure your load balancer with
Upgradeheaders andleast_connbalancing. - Run each node under Supervisor with graceful shutdown via
stopwaitsecs. - Monitor channel counts per node via the Reverb REST API to catch pub/sub failures early.