Laravel Reverb WebSocket Scaling: Channels, Presence, and Horizontal Deployment
#laravel #reverb #websockets #redis #broadcasting

Laravel Reverb WebSocket Scaling: Channels, Presence, and Horizontal Deployment

4 min read Mohamed Said Mohamed Said

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.timeout to evict stale members from crashed nodes.
  • Configure your load balancer with Upgrade headers and least_conn balancing.
  • 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.

Found this useful?

Frequently Asked Questions

3 questions
Q01 Do I need a Redis cluster, or will a single Redis instance work for Reverb scaling?
A single Redis instance is sufficient for most deployments. Redis pub/sub is extremely fast and the message volume from WebSocket broadcasts is typically low. Only move to a Redis cluster if your Redis instance itself becomes a bottleneck, which is rare before thousands of concurrent connections.
Q02 What happens to connected clients when a Reverb node restarts?
Reverb sends a WebSocket close frame on SIGTERM. Laravel Echo and Pusher-compatible clients have built-in reconnect logic, so clients will automatically reconnect to another available node within a few seconds. The presence channel will emit a `leaving` event for the disconnected client and a `joining` event when they reconnect.
Q03 Can I run Reverb behind AWS ALB or Cloudflare?
Yes. AWS ALB supports WebSocket upgrades natively — enable sticky sessions only if you cannot use the Redis scaling driver. Cloudflare proxies WebSockets on paid plans; ensure your Cloudflare timeout settings exceed your expected connection duration, as the default 100-second idle timeout will drop long-lived connections.

Continue reading

More Articles

View all