I run Laravel Reverb in production for my current SaaS. Real users, real classes happening in real time, real parents refreshing dashboards. The docs get you to "it works on my machine" in an hour. Production is a different animal. Here's what I had to learn the harder way.
Reverb is the easy part
The Reverb server itself has been the most boring component in my stack — it starts, it stays up, it speaks the Pusher protocol so Echo just works. Almost every "Reverb problem" I debugged turned out to live somewhere else: nginx, the queue, or my own event design.
Internalise this early: your realtime stack is Reverb + your reverse proxy + your queue workers + Redis, and it's only as reliable as the flakiest of the four.
nginx will silently kill your connections
The classic first-week bug: everything works, then connections start dying after exactly 60 seconds. That's nginx's default proxy_read_timeout closing "idle" upgraded connections.
You need the upgrade headers and longer timeouts on the WebSocket location:
location /app {
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_read_timeout 300s;
proxy_send_timeout 300s;
}
Reverb sends pings, so 300s is plenty. If you terminate TLS at nginx (you should), Reverb itself can stay plain HTTP on localhost — don't double-encrypt inside your own box.
Broadcasting happens on the queue — so the queue is now realtime infrastructure
ShouldBroadcast events don't go straight to Reverb. They go to your queue, and a worker delivers them. Which means:
- If your queue is backed up, your "realtime" updates arrive whenever the backlog clears. I've watched a busy import job delay live class updates by two minutes — technically nothing was broken, and everything was broken.
- If your worker dies silently (they do — I've written about this before), realtime just... stops, while the WebSocket connections stay green and healthy-looking.
Two mitigations that earn their keep: put broadcasts on their own queue (broadcast_queue in config/broadcasting.php or per-event broadcastQueue()), so a heavy job can't starve them; and use ShouldBroadcastNow for the handful of events where a two-second delay is worse than the dispatch cost.
Design events for reconnection, not just delivery
Mobile networks, sleeping laptops, elevator rides — connections drop constantly and Echo reconnects quietly. The question isn't whether clients miss events; it's what happens after they do.
The rule that saved me: events are notifications, not state. Broadcast "class 42 changed", let the client refetch the truth over HTTP. The moment you broadcast state deltas and apply them client-side, every missed event is silent corruption. My UI listens, then pulls. Boring, and correct after every reconnect for free.
Odds and ends worth knowing
- Redis pub/sub scaling works, but you probably don't need it yet. A single Reverb process on a modest VPS holds thousands of idle connections without breaking a sweat. Measure before you shard.
- Private channel auth hits your Laravel app on every subscribe. If your
routes/channels.phpclosures do DB queries, that's a query per tab per reconnect. Keep them cheap; cache what you can. - Supervise it like a queue worker. Whether that's Supervisor, systemd, or a Docker restart policy — Reverb is a long-running process, and long-running processes get restarted. Deploys must restart it too, or it keeps serving the old code's event classes.
None of this is exotic. That's rather the point — Reverb made WebSockets boring enough that the remaining problems are the ordinary ones: proxies, queues, and designing for failure. Those you have to solve yourself.