Home PHP Master Laravel Reverb: Real-Time WebSocket Server for Production
Advanced 4 min · July 13, 2026

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.

N
Naren Founder & Principal Engineer

20+ years shipping production PHP systems at scale. Drawn from code that ran under real load.

Follow
Production
production tested
July 13, 2026
last updated
2,073
articles · all by Naren
Before you start⏱ 20-25 min read
  • Laravel 10 or later
  • PHP 8.1 or later
  • Composer
  • Node.js and NPM (for client-side Echo)
  • Redis (for scaling)
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • 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.
✦ Definition~90s read
What is Laravel Reverb WebSocket Server?

Laravel Reverb is a first-party WebSocket server for Laravel that enables real-time broadcasting using the Pusher protocol, leveraging PHP Fibers for high concurrency.

Think of Laravel Reverb as a walkie-talkie system for your web app.
Plain-English First

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.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

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 ``

``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 ``

``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.

config/reverb.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

return [
    'apps' => [
        [
            'app_id' => env('REVERB_APP_ID'),
            'key' => env('REVERB_APP_KEY'),
            'secret' => env('REVERB_APP_SECRET'),
            'capacity' => null,
            'enable_client_messages' => false,
            'enable_statistics' => true,
        ],
    ],
    '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',
        ],
    ],
    'host' => env('REVERB_HOST', '0.0.0.0'),
    'port' => env('REVERB_PORT', 8080),
    'scheme' => env('REVERB_SCHEME', 'http'),
    'max_request_size' => 10000,
    'ping_interval' => 30,
    'close_on_disconnect' => false,
];
⚠ Production Security
📊 Production Insight
In production, always run Reverb behind a reverse proxy. Also, set REVERB_SCHEME=https if using SSL.
🎯 Key Takeaway
Install Reverb via Composer, publish config, generate app keys, and start the server. Use Redis for scaling in production.

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'); } } ```

```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'], }); ```

``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.

app/Events/MessageSent.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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');
    }
}
💡Queue Worker Required
📊 Production Insight
Use separate queue workers for Reverb to avoid blocking other jobs. Monitor queue length with Horizon.
🎯 Key Takeaway
Define events implementing ShouldBroadcast, dispatch them, and listen on the client with Echo. Always run the queue worker.

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 }); ```

``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); }); ``

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(); ``

routes/channels.phpPHP
1
2
3
4
5
6
7
8
9
10
11
<?php

use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('chat.{roomId}', function ($user, $roomId) {
    return $user->canJoinRoom($roomId);
});

Broadcast::channel('presence-room.{roomId}', function ($user, $roomId) {
    return $user->canJoinRoom($roomId) ? ['id' => $user->id, 'name' => $user->name] : false;
});
🔥CSRF Protection
📊 Production Insight
Cache channel authorization logic if it involves database queries. Use Redis to store user permissions for faster checks.
🎯 Key Takeaway
Private and Presence channels require authorization logic in channels.php. Use 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.

``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; } } ```

supervisor/reverb.confPHP
1
2
3
4
5
6
7
8
9
[program:reverb]
command=php /home/forge/example.com/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=/home/forge/example.com/storage/logs/reverb.log
⚠ Redis Persistence
📊 Production Insight
Monitor Redis memory usage. If using a shared Redis, ensure it has enough capacity. Consider using Redis Sentinel for high availability.
🎯 Key Takeaway
Enable scaling in config, use Redis as pub/sub, and run multiple Reverb instances behind a load balancer.

Security Best Practices

Securing your WebSocket server is critical. Here are key practices:

  1. Use HTTPS/WSS: Always serve WebSocket over TLS. Configure your reverse proxy to handle SSL and set REVERB_SCHEME=https.
  2. Authenticate Private Channels: Never expose private data without authorization. Use Laravel's built-in channel authorization.
  3. 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; } } ```

  1. Disable Client Messages: Unless needed, set enable_client_messages to false in config to prevent clients from sending arbitrary events.
  2. Validate Input: When broadcasting events, sanitize data. For example, strip HTML tags from chat messages.
  3. Use Separate App Credentials: For different environments (staging, production), use different app IDs and keys.
  4. 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]); }); ``

app/Providers/EventServiceProvider.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use Laravel\Reverb\Events\ConnectionEstablished;
use Laravel\Reverb\Events\ConnectionClosed;

class EventServiceProvider extends ServiceProvider
{
    public function boot()
    {
        parent::boot();

        Event::listen(ConnectionEstablished::class, function ($event) {
            \Log::info('WebSocket connected', ['connection_id' => $event->connection->id]);
        });

        Event::listen(ConnectionClosed::class, function ($event) {
            \Log::info('WebSocket disconnected', ['connection_id' => $event->connection->id]);
        });
    }
}
💡CORS Configuration
📊 Production Insight
Consider using a Web Application Firewall (WAF) to protect against DDoS attacks. Also, set connection limits per IP at the proxy level.
🎯 Key Takeaway
Use WSS, authenticate channels, rate limit, disable client messages, validate input, and monitor connections.

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.

``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.

Common debugging steps
  • Check if Reverb server is running: ps aux | grep reverb
  • Check queue worker: php artisan queue:status
  • Test broadcast manually: php artisan tinker then event(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.

config/reverb.php (logging section)PHP
1
2
3
4
'logging' => [
    'enabled' => env('REVERB_LOGGING_ENABLED', false),
    'channel' => env('REVERB_LOGGING_CHANNEL', 'stack'),
],
🔥Heartbeat and Ping
📊 Production Insight
Set up alerts for queue failures and connection drops. Use a tool like Laravel Pulse for real-time metrics.
🎯 Key Takeaway
Use reverb:stats, enable logging, monitor queue workers, and check WebSocket frames for debugging.

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 ``

``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 ``

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.

/etc/supervisor/conf.d/reverb.confPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[program:reverb]
command=php /home/forge/example.com/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=/home/forge/example.com/storage/logs/reverb.log

[program:reverb-worker]
command=php /home/forge/example.com/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
redirect_stderr=true
stdout_logfile=/home/forge/example.com/storage/logs/reverb-worker.log
⚠ Port Availability
📊 Production Insight
Set up health checks for Reverb. For example, a simple TCP check on the port. Use a monitoring tool like New Relic or Datadog.
🎯 Key Takeaway
Use Supervisor to keep Reverb and queue workers running. Configure multiple processes for scaling.
● Production incidentPOST-MORTEMseverity: high

The Silent Disconnect: When Reverb Events Went Missing

Symptom
Users reported that real-time notifications stopped working after a deployment. No errors in logs, but events never reached clients.
Assumption
Developers assumed the Reverb server was misconfigured or the WebSocket connection was dropping.
Root cause
The queue worker for the 'reverb' queue was not running. Reverb uses queues to dispatch broadcast events asynchronously. Without the worker, events were queued but never processed.
Fix
Start the queue worker: php artisan queue:work --queue=reverb. Also added a supervisor configuration to ensure the worker restarts automatically.
Key lesson
  • 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.
Production debug guideSymptom to Action4 entries
Symptom · 01
WebSocket connection fails with 403
Fix
Check broadcasting auth endpoint and CSRF token. Ensure user is authenticated for private channels.
Symptom · 02
Events not received by clients
Fix
Check queue worker is running for 'reverb' queue. Verify event is broadcasted on correct channel.
Symptom · 03
High memory usage on Reverb server
Fix
Check for memory leaks in event listeners. Reduce number of concurrent connections per server or scale horizontally.
Symptom · 04
Client disconnects intermittently
Fix
Check network stability and WebSocket ping/pong settings. Increase heartbeat interval in Reverb config.
★ Quick Debug Cheat SheetImmediate actions for common Reverb issues.
Connection refused
Immediate action
Check if Reverb server is running
Commands
ps aux | grep reverb
php artisan reverb:start
Fix now
Start Reverb server
Events not broadcasting+
Immediate action
Check queue worker
Commands
php artisan queue:work --queue=reverb --tries=3
php artisan queue:listen --queue=reverb
Fix now
Run queue worker
Auth errors+
Immediate action
Check broadcasting auth route
Commands
php artisan route:list | grep broadcasting
Check Broadcast::channel definition
Fix now
Define channel authorization logic
FeatureLaravel ReverbPusherSocket.io (Node.js)
LanguagePHP (native)Third-party serviceNode.js
Setup ComplexityLow (Composer + Artisan)Medium (account + keys)High (Node.js + npm)
ScalingRedis pub/subAutomatic (paid)Custom (Redis adapter)
ConcurrencyFibersN/A (service)Event loop
CostFree (self-hosted)Paid tiersFree (self-hosted)
Laravel IntegrationFirst-partyVia packageCustom
⚙ Quick Reference
7 commands from this guide
FileCommand / CodePurpose
configreverb.phpreturn [Installation and Configuration
appEventsMessageSent.phpnamespace App\Events;Broadcasting Events with Reverb
routeschannels.phpuse Illuminate\Support\Facades\Broadcast;Private and Presence Channels
supervisorreverb.conf[program:reverb]Scaling Reverb with Redis
appProvidersEventServiceProvider.phpnamespace App\Providers;Security Best Practices
configreverb.php (logging section)'logging' => [Monitoring and Debugging
etcsupervisorconf.dreverb.conf[program:reverb]Deployment with Supervisor

Key takeaways

1
Laravel Reverb provides a native, high-performance WebSocket server for real-time features.
2
Always run the queue worker for the 'reverb' queue to process broadcast events.
3
Use private and presence channels for authenticated data, and secure them with authorization logic.
4
Scale horizontally using Redis pub/sub and multiple Reverb instances behind a load balancer.
5
Monitor connections and queue health to ensure reliability.

Common mistakes to avoid

4 patterns
×

Forgetting to run the queue worker

×

Using public channels for sensitive data

×

Not enabling Redis scaling in production

×

Exposing Reverb directly to the internet

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does Laravel Reverb handle concurrency?
Q02SENIOR
What is the role of the queue worker in Reverb?
Q03SENIOR
How do you secure private channels in Reverb?
Q01 of 03SENIOR

How does Laravel Reverb handle concurrency?

ANSWER
Reverb uses PHP Fibers to handle multiple connections concurrently within a single process, allowing it to manage thousands of connections without blocking.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is Laravel Reverb?
02
Do I need Pusher to use Reverb?
03
How do I scale Reverb?
04
Why are my events not broadcasting?
05
Can I use Reverb with other PHP frameworks?
N
Naren Founder & Principal Engineer

20+ years shipping production PHP systems at scale. Drawn from code that ran under real load.

Follow
Verified
production tested
July 13, 2026
last updated
2,073
articles · all by Naren
🔥

That's Laravel. Mark it forged?

4 min read · try the examples if you haven't

Previous
Laravel Broadcasting
16 / 24 · Laravel
Next
Laravel Folio and Volt: File-Based Routing and Livewire Components