Master Laravel Reverb: Real-Time WebSocket Server for Production
Learn to build and deploy Laravel Reverb WebSocket server with production-ready code, debugging tips, and security best practices.
20+ years shipping production PHP systems at scale. Drawn from code that ran under real load.
- ✓Laravel 10 or later
- ✓PHP 8.1 or later
- ✓Composer
- ✓Node.js and NPM (for client-side Echo)
- ✓Redis (for scaling)
- Laravel Reverb is a first-party WebSocket server for real-time features.
- It replaces Pusher and Socket.io with native Laravel integration.
- Uses Laravel Broadcasting and Events for easy setup.
- Supports horizontal scaling via Redis.
- Optimized for high performance with PHP 8.1+ and Fibers.
Think of Laravel Reverb as a walkie-talkie system for your web app. Normally, your app is like a library where you have to walk to the desk to get updates. With Reverb, it's like having a walkie-talkie that instantly broadcasts messages to everyone in the building. When one user does something (like sending a chat message), Reverb instantly tells all other users without them having to refresh the page.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
Real-time features like live chat, notifications, and collaborative editing are no longer luxuries—they're expectations. Traditionally, PHP developers relied on third-party services like Pusher or complex setups with Node.js and Socket.io. Laravel Reverb changes the game by providing a first-party, high-performance WebSocket server built specifically for Laravel, leveraging PHP 8.1+ features like Fibers for concurrency.
Reverb integrates seamlessly with Laravel's existing Broadcasting system, Events, and Queues. You can broadcast events to channels, listen for them on the client side using Laravel Echo, and scale horizontally using Redis. This means you can handle thousands of concurrent connections without breaking a sweat.
In this tutorial, you'll learn how to install, configure, and deploy Laravel Reverb in production. We'll cover authentication, private channels, presence channels, scaling, and debugging. You'll also see a real-world incident where a missing queue worker caused silent failures, and how to avoid it. By the end, you'll be able to build robust real-time features with confidence.
Installation and Configuration
To get started with Laravel Reverb, you need Laravel 10 or later and PHP 8.1+. Install Reverb via Composer:
composer require laravel/reverb
Next, publish the configuration file:
This creates Generate the app credentials using the Artisan command: This updates your For production, you should run this as a daemon using Supervisor. Reverb works with Laravel's Broadcasting system. First, define an event that implements ```php
namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class MessageSent implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public $message; public function __construct($message) { $this->message = $message; } public function broadcastOn() { return new Channel('chat'); } } ``` To broadcast, dispatch the event: ```php use App\Events\MessageSent; $message = 'Hello, world!'; event(new MessageSent($message)); ``` On the client side, use Laravel Echo with the Reverb connector. Install Echo and the Reverb connector: `` Note: Reverb uses the Pusher protocol, so we use Configure Echo in your JavaScript: ```javascript import Echo from 'laravel-echo'; import Pusher from 'pusher-js'; window.Pusher = Pusher; window.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'], }); ``` Listen for events: `` Make sure to define the environment variables in your For authenticated channels, use Private and Presence channels. Define authorization logic in ```php use Illuminate\Support\Facades\Broadcast; Broadcast::channel('chat.{roomId}', function ($user, $roomId) { return $user->canJoinRoom($roomId); // Custom logic }); ``` Then, create an event that broadcasts on a private channel: `` On the client, subscribe to the private channel: `` Presence channels add user information. Define: `` Client subscription: `` Ensure the broadcasting auth endpoint is enabled. By default, Laravel provides `` Reverb supports horizontal scaling using Redis as a pub/sub backend. When scaling, multiple Reverb server instances communicate via Redis to broadcast events across all connections. Enable scaling in `` Ensure Redis is installed and running. Then, start multiple Reverb instances on different ports or servers. For example, using Supervisor: `` Each instance will share the same app credentials and use Redis to synchronize. Clients can connect to any instance; the pub/sub ensures all instances receive broadcasts. To load balance, use a reverse proxy like Nginx with sticky sessions (optional but recommended for performance): ```nginx upstream reverb { least_conn; server 127.0.0.1:8080; server 127.0.0.1:8081; } server { listen 443 ssl; server_name reverb.example.com; location / { 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_set_header X-Real-IP $remote_addr; } } ``` Securing your WebSocket server is critical. Here are key practices: ```nginx limit_req_zone $binary_remote_addr zone=reverb:10m rate=10r/s; server { location /app/ { limit_req zone=reverb burst=20 nodelay; proxy_pass http://reverb; } } ``` `` Production monitoring is essential. Reverb provides statistics via the `` This shows connected clients, channels, and memory usage. For real-time monitoring, enable statistics in config and use a dashboard. You can also integrate with Laravel Horizon to monitor queue jobs. Since broadcasts are queued, Horizon shows failed jobs and throughput. For debugging, enable logging in Reverb: `` Set If events aren't received, check the queue worker logs. Also, ensure the event is broadcasted on the correct channel and the client is subscribed. For production, run Reverb as a daemon using Supervisor. Create a configuration file: `` Then, update Supervisor: `` Ensure the queue worker is also supervised: `` For zero-downtime deployments, use a load balancer and gracefully stop old instances. You can send SIGTERM to Reverb processes; they will finish ongoing broadcasts before exiting. Also, consider using a process manager like Laravel Forge or Envoyer for automated deployments.php artisan vendor:publish --tag=reverb-config
config/reverb.php. The default configuration uses an in-memory array for app management, but for production you should use Redis. Update your .env file:BROADCAST_DRIVER=reverb
REVERB_APP_ID=my-app-id
REVERB_APP_KEY=my-app-key
REVERB_APP_SECRET=my-app-secret
REVERB_HOST=0.0.0.0
REVERB_PORT=8080
REVERB_SCHEME=http
REVERB_REDIS_HOST=127.0.0.1
REVERB_REDIS_PASSWORD=null
REVERB_REDIS_PORT=6379php artisan reverb:generate.env with the keys. Now, start the Reverb server:php artisan reverb:startREVERB_SCHEME=https if using SSL.Broadcasting Events with Reverb
ShouldBroadcast:bash npm install --save-dev laravel-echo pusher-js ``pusher-js.javascript Echo.channel('chat') .listen('MessageSent', (e) => { console.log(e.message); }); ``.env and prefix with VITE_ for Vite.Private and Presence Channels
routes/channels.php:php public function broadcastOn() { return new PrivateChannel('chat.' . $this->roomId); } ``javascript Echo.private('chat.' + roomId) .listen('MessageSent', (e) => { console.log(e.message); }); ``php Broadcast::channel('presence-room.{roomId}', function ($user, $roomId) { return $user->canJoinRoom($roomId) ? ['id' => $user->id, 'name' => $user->name] : false; }); ``javascript Echo.join('presence-room.' + roomId) .here((users) => { console.log('Users in room:', users); }) .joining((user) => { console.log(user.name + ' joined'); }) .leaving((user) => { console.log(user.name + ' left'); }) .listen('MessageSent', (e) => { console.log(e.message); }); ``/broadcasting/auth. Make sure your routes include it:php // In routes/api.php or routes/web.php Broadcast::routes(); ``Echo.private() and Echo.join() accordingly.Scaling Reverb with Redis
config/reverb.php:php 'scaling' => [ 'enabled' => true, 'channel' => 'reverb', 'server' => [ 'url' => env('REVERB_REDIS_HOST'), 'host' => env('REVERB_REDIS_HOST', '127.0.0.1'), 'port' => env('REVERB_REDIS_PORT', 6379), 'password' => env('REVERB_REDIS_PASSWORD'), 'database' => '0', ], ], ``ini [program:reverb-1] command=php /path/to/artisan reverb:start --port=8080 process_name=%(program_name)s_%(process_num)02d numprocs=2 autostart=true autorestart=true user=forge ``Security Best Practices
REVERB_SCHEME=https.enable_client_messages to false in config to prevent clients from sending arbitrary events.php Event::listen(\Laravel\Reverb\Events\ConnectionEstablished::class, function ($event) { Log::info('WebSocket connected', ['connection_id' => $event->connection->id]); }); ``Monitoring and Debugging
reverb:stats command:bash php artisan reverb:stats ``php // config/reverb.php 'logging' => [ 'enabled' => env('REVERB_LOGGING_ENABLED', false), 'channel' => env('REVERB_LOGGING_CHANNEL', 'stack'), ], ``REVERB_LOGGING_ENABLED=true in .env to see detailed logs.ps aux | grep reverbphp artisan queue:statusphp artisan tinker then event(new \App\Events\MessageSent('test'))Deployment with Supervisor
ini [program:reverb] command=php /path/to/artisan reverb:start --port=8080 process_name=%(program_name)s_%(process_num)02d numprocs=2 autostart=true autorestart=true user=forge redirect_stderr=true stdout_logfile=/path/to/storage/logs/reverb.log ``bash sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start reverb:* ``ini [program:reverb-worker] command=php /path/to/artisan queue:work --queue=reverb --sleep=3 --tries=3 process_name=%(program_name)s_%(process_num)02d numprocs=2 autostart=true autorestart=true user=forge ``
The Silent Disconnect: When Reverb Events Went Missing
php artisan queue:work --queue=reverb. Also added a supervisor configuration to ensure the worker restarts automatically.- Always verify queue workers are running for async broadcast drivers.
- Monitor queue health with tools like Laravel Horizon or custom checks.
- Use supervisor or similar to keep workers alive.
- Add logging to broadcast events to trace failures.
- Test deployment with a staging environment that mirrors production.
ps aux | grep reverbphp artisan reverb:start| File | Command / Code | Purpose |
|---|---|---|
| config | return [ | Installation and Configuration |
| app | namespace App\Events; | Broadcasting Events with Reverb |
| routes | use Illuminate\Support\Facades\Broadcast; | Private and Presence Channels |
| supervisor | [program:reverb] | Scaling Reverb with Redis |
| app | namespace App\Providers; | Security Best Practices |
| config | 'logging' => [ | Monitoring and Debugging |
| [program:reverb] | Deployment with Supervisor |
Key takeaways
Interview Questions on This Topic
How does Laravel Reverb handle concurrency?
Frequently Asked Questions
20+ years shipping production PHP systems at scale. Drawn from code that ran under real load.
That's Laravel. Mark it forged?
4 min read · try the examples if you haven't