6 min read

Laravel Reverb: Real-Time WebSockets Without the Infrastructure Tax

Laravel Reverb gives PHP developers a self-hosted WebSocket server that plugs directly into Laravel's broadcasting system — no Pusher account required.

Featured image for "Laravel Reverb: Real-Time WebSockets Without the Infrastructure Tax"

For years, adding real-time features to a Laravel application meant making a decision you could feel in your wallet: pay Pusher for managed WebSockets, wrestle with open-source alternatives like Soketi (often under-maintained), or attempt to bolt on a Node.js process alongside your PHP app and accept the operational complexity that came with it. None of these options was particularly clean. Laravel Reverb changes the calculus entirely.

Released as a first-party package alongside Laravel 11, Reverb is a high-performance WebSocket server written in PHP — built specifically to integrate with Laravel’s existing broadcasting system and designed to run as a self-hosted process you control. It speaks the Pusher protocol, which means the entire Laravel Echo frontend ecosystem, every existing channel authentication pattern, and all of your existing ShouldBroadcast events work without a single line of change. You drop in a new server and keep everything else.

What Reverb Actually Is

Under the hood, Reverb is built on ReactPHP, giving it an event-loop-based non-blocking architecture. It runs as a long-lived process alongside your application — separate from PHP-FPM or your web server — and handles WebSocket connections independently of the request lifecycle. This means your broadcasting logic stays in PHP, your frontend still uses Laravel Echo, and your events fire through the same broadcast() helper you already know.

Because it implements the Pusher protocol natively, you can switch from Pusher to Reverb by changing a few environment variables and not touching a single line of application code.

Installation

composer require laravel/reverb
php artisan reverb:install

The install command publishes a config/reverb.php file and updates your .env:

REVERB_APP_ID=my-app
REVERB_APP_KEY=my-app-key
REVERB_APP_SECRET=my-app-secret
REVERB_HOST=0.0.0.0
REVERB_PORT=8080
REVERB_SCHEME=http

BROADCAST_CONNECTION=reverb

Update the frontend to point Echo at your Reverb server:

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,
    wssPort: import.meta.env.VITE_REVERB_PORT,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});

Start the server:

php artisan reverb:start

That is genuinely the full setup for local development.

Broadcasting an Event

The broadcasting side is unchanged if you have done this before with Pusher. Create a broadcastable event:

php artisan make:event OrderShipped
<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class OrderShipped implements ShouldBroadcast
{
    use InteractsWithSockets, SerializesModels;

    public function __construct(
        public readonly Order $order,
    ) {}

    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('orders.' . $this->order->user_id),
        ];
    }

    public function broadcastWith(): array
    {
        return [
            'order_id' => $this->order->id,
            'status'   => $this->order->status,
            'shipped_at' => $this->order->shipped_at->toISOString(),
        ];
    }
}

Dispatch it from anywhere in your application:

OrderShipped::dispatch($order);

On the frontend, a few lines of Echo is all that is needed:

Echo.private(`orders.${userId}`)
    .listen('OrderShipped', (event) => {
        console.log(`Order ${event.order_id} is now ${event.status}`);
        updateOrderStatus(event);
    });

Channels: Public, Private, and Presence

Reverb supports all three channel types that Laravel’s broadcasting system defines.

Public channels require no authentication — anyone connected can subscribe:

Echo.channel('announcements')
    .listen('NewAnnouncement', (event) => {
        showBanner(event.message);
    });

Private channels authenticate through your application’s /broadcasting/auth endpoint. Define authorization in routes/channels.php:

Broadcast::channel('orders.{userId}', function (User $user, int $userId) {
    return $user->id === $userId;
});

Presence channels take this further — they track which authenticated users are currently subscribed, making them ideal for collaborative features like “who else is viewing this document” or live user lists:

Broadcast::channel('document.{documentId}', function (User $user, int $documentId) {
    if ($user->canAccess(Document::find($documentId))) {
        return ['id' => $user->id, 'name' => $user->name];
    }
});
Echo.join(`document.${documentId}`)
    .here((users) => {
        renderCollaboratorList(users);
    })
    .joining((user) => {
        addCollaborator(user);
    })
    .leaving((user) => {
        removeCollaborator(user);
    });

Running Reverb in Production

For production, Reverb runs as a persistent process. The recommended approach is to manage it with Supervisor so it restarts automatically on failure:

[program:reverb]
process_name=%(program_name)s
command=php /var/www/html/artisan reverb:start --host=0.0.0.0 --port=8080
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/reverb.log

For TLS termination, sit Reverb behind Nginx and proxy the WebSocket connection:

server {
    listen 443 ssl;
    server_name example.com;

    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_set_header       Host $host;
        proxy_read_timeout     60s;
    }
}

Set REVERB_SCHEME=https and REVERB_PORT=443 in your environment and Echo handles the rest.

Scaling Horizontally

Reverb ships with a scaling solution built around Laravel’s existing queue infrastructure. Because it uses Redis for horizontal message passing, multiple Reverb nodes can serve connections simultaneously without coordinating directly. Configure it in config/reverb.php:

'servers' => [
    'reverb' => [
        // ...
        'scaling' => [
            'enabled' => true,
            'channel' => 'reverb',
            'server'  => [
                'url' => env('REDIS_URL'),
            ],
        ],
    ],
],

With scaling enabled, you can run multiple Reverb instances behind a load balancer. Events broadcast from any application server are picked up and forwarded to all WebSocket connections regardless of which node holds them.

The Honest Trade-offs

Reverb is not a managed service. You are responsible for keeping the process running, monitoring memory usage under sustained connection load, and tuning the connection limits for your server. A single Reverb process handles thousands of concurrent connections comfortably on modest hardware, but sustained high connection counts require attention to your OS-level file descriptor limits (ulimit -n).

For applications that need global edge delivery or have strict uptime SLAs where operational overhead is a non-starter, Pusher still makes sense. But for most applications running on a VPS or a container cluster — or for teams that want their real-time infrastructure to live in the same codebase as the application it serves — Reverb eliminates a significant external dependency without asking you to rewrite anything.

The combination of zero protocol changes, full Laravel Echo compatibility, and horizontal Redis scaling makes it the most practical WebSocket solution the PHP ecosystem has had. If you have been deferring real-time features because the setup cost felt too high, Reverb removes that excuse.

Resources