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:
``bash composer require laravel/reverb ``
Next, publish the configuration file:
``bash php artisan vendor:publish --tag=reverb-config ``
This creates 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:
``env 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=6379 ``
Generate the app credentials using the Artisan command:
``bash php artisan reverb:generate ``
This updates your .env with the keys. Now, start the Reverb server:
``bash php artisan reverb:start ``
For production, you should run this as a daemon using Supervisor.
REVERB_SCHEME=https if using SSL.Broadcasting Events with Reverb
Reverb works with Laravel's Broadcasting system. First, define an event that implements ShouldBroadcast:
```php <?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:
``bash npm install --save-dev laravel-echo pusher-js ``
Note: Reverb uses the Pusher protocol, so we use pusher-js.
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:
``javascript Echo.channel('chat') .listen('MessageSent', (e) => { console.log(e.message); }); ``
Make sure to define the environment variables in your .env and prefix with VITE_ for Vite.
Private and Presence Channels
For authenticated channels, use Private and Presence channels. Define authorization logic in routes/channels.php:
```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:
``php public function broadcastOn() { return new PrivateChannel('chat.' . $this->roomId); } ``
On the client, subscribe to the private channel:
``javascript Echo.private('chat.' + roomId) .listen('MessageSent', (e) => { console.log(e.message); }); ``
Presence channels add user information. Define:
``php Broadcast::channel('presence-room.{roomId}', function ($user, $roomId) { return $user->canJoinRoom($roomId) ? ['id' => $user->id, 'name' => $user->name] : false; }); ``
Client subscription:
``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); }); ``
Ensure the broadcasting auth endpoint is enabled. By default, Laravel provides /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
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 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', ], ], ``
Ensure Redis is installed and running. Then, start multiple Reverb instances on different ports or servers. For example, using Supervisor:
``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 ``
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; } } ```
Security Best Practices
Securing your WebSocket server is critical. Here are key practices:
- Use HTTPS/WSS: Always serve WebSocket over TLS. Configure your reverse proxy to handle SSL and set
REVERB_SCHEME=https. - Authenticate Private Channels: Never expose private data without authorization. Use Laravel's built-in channel authorization.
- Rate Limiting: Reverb doesn't have built-in rate limiting. Implement it at the application level or use a reverse proxy like Nginx:
```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; } } ```
- Disable Client Messages: Unless needed, set
enable_client_messagesto false in config to prevent clients from sending arbitrary events. - Validate Input: When broadcasting events, sanitize data. For example, strip HTML tags from chat messages.
- Use Separate App Credentials: For different environments (staging, production), use different app IDs and keys.
- Monitor Connections: Log connection/disconnection events to detect anomalies. Use Laravel's event listeners:
``php Event::listen(\Laravel\Reverb\Events\ConnectionEstablished::class, function ($event) { Log::info('WebSocket connected', ['connection_id' => $event->connection->id]); }); ``
Monitoring and Debugging
Production monitoring is essential. Reverb provides statistics via the reverb:stats command:
``bash php artisan reverb:stats ``
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:
``php // config/reverb.php 'logging' => [ 'enabled' => env('REVERB_LOGGING_ENABLED', false), 'channel' => env('REVERB_LOGGING_CHANNEL', 'stack'), ], ``
Set REVERB_LOGGING_ENABLED=true in .env to see detailed logs.
- Check if Reverb server is running:
ps aux | grep reverb - Check queue worker:
php artisan queue:status - Test broadcast manually:
php artisan tinkerthenevent(new \App\Events\MessageSent('test')) - Verify client connection: Use browser DevTools Network tab to see WebSocket frames.
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.
Deployment with Supervisor
For production, run Reverb as a daemon using Supervisor. Create a configuration file:
``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 ``
Then, update Supervisor:
``bash sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start reverb:* ``
Ensure the queue worker is also supervised:
``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 ``
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.
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
Common mistakes to avoid
4 patternsForgetting to run the queue worker
Using public channels for sensitive data
Not enabling Redis scaling in production
Exposing Reverb directly to the internet
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