Introduction: The WebSocket Revolution in Laravel
For years, building real-time interactive features in Laravel—such as live multiplayer quiz battles, instant notifications, collaborative dashboards, or real-time leaderboard score feeds—required either paying for expensive hosted third-party services like Pusher or Ably, or maintaining complex Node.js/Socket.io sidecar containers.
With the release of Laravel Reverb, Laravel introduced an official, first-party, high-performance WebSocket server engineered specifically for the Laravel ecosystem.
Built with PHP and utilizing asynchronous event loops, Reverb can handle tens of thousands of concurrent WebSocket connections on a single server, integrates seamlessly with Laravel Echo, and costs zero dollars in third-party API subscription fees.
In this deep-dive guide, we explore how to install, configure, scale, and secure Laravel Reverb for real-time applications like QuizRush.
1. Installing and Configuring Laravel Reverb
Reverb is bundled directly with modern Laravel versions. To install Reverb:
php artisan install:broadcasting
This interactive command installs the laravel/reverb package, publishes config/reverb.php, and configures your .env environment variables automatically:
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=quizrush-app
REVERB_APP_KEY=reverb-key-xyz123
REVERB_APP_SECRET=reverb-secret-abc456
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
To start the local Reverb WebSocket daemon:
php artisan reverb:start --debug
2. Defining Real-Time Broadcast Events
In Laravel, any class that implements the ShouldBroadcast interface is automatically serialized and dispatched over the WebSocket server whenever the event is fired:
namespace App\Events;
use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class LeaderboardUpdatedEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(
public readonly User $player,
public readonly int $pointsEarned,
public readonly int $newRankPosition
) {}
/**
* Define the public or presence channel the event broadcasts on.
*/
public function broadcastOn(): array
{
return [
new Channel('public-leaderboard'),
];
}
/**
* Customize the broadcast event name for frontend listeners.
*/
public function broadcastAs(): string
{
return 'leaderboard.score.updated';
}
/**
* Payload sent to connected WebSocket clients.
*/
public function broadcastWith(): array
{
return [
'player_id' => $this->player->id,
'player_name' => $this->player->name,
'tier_badge' => $this->player->tier_badge,
'points_earned' => $this->pointsEarned,
'new_rank' => $this->newRankPosition,
'timestamp' => now()->toIso8601String(),
];
}
}
When a user completes a round in your controller or service:
// Dispatch event: automatically queues and broadcasts to all WebSocket subscribers
broadcast(new LeaderboardUpdatedEvent($user, 45, 3))->toOthers();
3. Securing Private and Presence Channels
For multiplayer quiz rooms or private user notifications, you do not want data broadcast over public channels. Reverb fully supports Laravel's authorization policies for Private Channels and Presence Channels.
Step 1: Define the Channel Authorization in routes/channels.php
use App\Models\User;
use App\Models\QuizRoom;
// Authorize private room access
Broadcast::channel('quiz-room.{roomId}', function (User $user, int $roomId) {
$room = QuizRoom::find($roomId);
return $room && $room->hasParticipant($user->id);
});
// Presence Channel: Track online players in the lobby
Broadcast::channel('trivia-lobby', function (User $user) {
return [
'id' => $user->id,
'name' => $user->name,
'tier' => $user->tier_badge,
];
});
4. Connecting Frontend Clients with Laravel Echo
On the frontend (React, Vue, or Vanilla JS), install Laravel Echo and the standard Pusher-JS client library (which Reverb emulates):
npm install --save laravel-echo pusher-js
Initializing Laravel Echo (resources/js/echo.js)
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
export const echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
Subscribing to Live Events in a React / Vue Component
import { useEffect, useState } from 'react';
import { echo } from './echo';
export function LiveLeaderboardTicker() {
const [recentUpdates, setRecentUpdates] = useState([]);
useEffect(() => {
// Subscribe to public channel
const channel = echo.channel('public-leaderboard');
channel.listen('.leaderboard.score.updated', (event) => {
console.log('Real-time score received:', event);
setRecentUpdates((prev) => [event, ...prev.slice(0, 9)]);
});
return () => {
echo.leaveChannel('public-leaderboard');
};
}, []);
return (
<div className="live-ticker">
<h4>⚡ Live Community Activity</h4>
<ul>
{recentUpdates.map((item, idx) => (
<li key={idx}>
<strong>{item.player_name}</strong> earned +{item.points_earned} pts! (Rank #{item.new_rank})
</li>
))}
</ul>
</div>
);
}
5. Scaling Reverb with Redis and Supervisor in Production
In high-concurrency production deployments (e.g., handling 20,000+ simultaneous quiz players):
20,000 Web Browsers ➔ Nginx / Caddy SSL Proxy (wss://) ➔ Laravel Reverb Worker Cluster ➔ Redis Pub/Sub Backbone ➔ Laravel Queue Workers
Configure Supervisor (/etc/supervisor/conf.d/reverb.conf)
[program:reverb]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/skill/artisan reverb:start --host=0.0.0.0 --port=8080
autostart=true
autorestart=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/skill/storage/logs/reverb.log
stopwaitsecs=3600
Laravel Reverb Architecture Checklist
- First-Party Simplicity: No third-party Pusher quotas or external API subscription costs.
- Queue Integration: Real-time events process asynchronously through Laravel Horizon queue workers.
- Channel Authorization: Secure private channels with standard Laravel policies in
routes/channels.php. - Scale via Redis Pub/Sub: Run multiple Reverb server instances behind an Nginx load balancer seamlessly.
Frequently Asked Questions (FAQ)
How many concurrent connections can Laravel Reverb handle?
On a standard modern 4-core, 8GB RAM cloud server, Laravel Reverb can sustain over 25,000 to 30,000 active concurrent WebSocket connections with sub-10ms event delivery latency.Do I need to change my frontend code if migrating from Pusher to Reverb?
No! Reverb implements the official Pusher WebSocket wire protocol. You simply update yourbroadcaster: 'reverb' configuration in Laravel Echo while keeping all your existing channel.listen() code untouched.
Does Reverb support SSL / WSS encryption?
Yes. In production, you typically terminate SSL (WSS) at your reverse proxy (Nginx, Caddy, or Cloudflare), which forwards decrypted traffic to Reverb onlocalhost:8080.