Skip to content
All posts

Your Laravel App Is Still Paying Pusher for No Reason

April 12, 2026·Read on Medium·

Reverb shipped with Laravel 11 in March 2024. It’s been stable for two years. The excuse to keep paying $49/month is gone.

Every Laravel app I’ve audited in the past two years has the same dependency in composer.json: pusher/pusher-php-server. And somewhere in the billing settings of every one of those companies is a recurring Pusher charge that nobody has questioned since the original developer set it up.

Pusher’s Startup plan costs $49/month. You get 500 concurrent connections and 1 million messages per day. For most applications, that’s plenty. It also means you’re paying $588/year to route real-time events through a third-party service when Laravel ships a first-party WebSocket server that you own and operate yourself.

That server is Reverb. It shipped as a stable, documented package in March 2024 alongside Laravel 11. Most teams either haven’t heard of it or decided it was too complicated to bother with at the time. Two years later, most of those teams are still on Pusher.

This is about why that assumption hasn’t aged well.

What Pusher Actually Is in Your Stack

If you’ve used Laravel Broadcasting, you know the pattern. Your app fires an event, it goes through a broadcasting driver, the driver pushes it to a channel and the JavaScript client listening on that channel receives it via a WebSocket connection.

Pusher is one driver for that. Ably is another. Laravel Echo doesn’t care which one sits underneath, it just needs something that speaks the Pusher protocol. The front-end abstraction is clean, which is exactly why swapping the driver is more straightforward than most people expect.

Pusher’s value proposition was always clear: you don’t run infrastructure. No WebSocket server process to manage, no long-running connections to worry about, no horizontal scaling problem to solve. You just ship your events and pay for the convenience of not thinking about it.

That was a legitimate trade when the alternative was rolling your own solution with Ratchet or swoole, or running a separate Node.js socket.io server alongside your PHP app. In 2019, paying Pusher was the pragmatic call.

In 2026, it’s inertia.

What Reverb Actually Is

Laravel Reverb is a first-party WebSocket server written in PHP. It’s maintained by the Laravel team, fully integrated with Laravel Broadcasting and speaks the Pusher protocol out of the box. That last part matters: your existing laravel-echo frontend code doesn't change when you switch drivers.

You install it with a single command:

php artisan install:broadcasting

That command installs the Reverb package, publishes the config and scaffolds the environment variables you need. Then you start the server:

php artisan reverb:start

From your application’s perspective, events broadcast identically. The driver changes in config/broadcasting.php and a few environment variables. Your ShouldBroadcast events, your Echo subscriptions, your channel authorization logic: all of it stays the same.

The Reverb server is a long-running PHP process. It handles WebSocket handshakes, maintains connections and routes messages to the appropriate channels. Under the hood it uses React PHP (the event-driven library, not Facebook’s UI framework) to handle concurrent connections without spawning a thread per connection.

The Deployment Concern Is the Real Hesitation

Most developers who skip Reverb aren’t skeptical about the protocol or the Laravel integration. The hesitation is operational: how do you run a long-running PHP process in production without it becoming a maintenance burden?

The answer is the same as how you run Laravel queues in production: Supervisor.

A minimal Supervisor configuration for Reverb looks like this:

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

If you’re already running Laravel Horizon for queues, Supervisor is not a new concept for your infrastructure. Reverb is just another process entry. You’re not adding a new class of problem, you’re adding another program block in a config file you already have.

Nginx proxies WebSocket connections to the Reverb port:

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_cache_bypass $http_upgrade;
}

SSL termination happens at Nginx as it does for every other request. Reverb itself doesn’t need to know about certificates. This is the same pattern you use for anything that listens on a local port.

None of this is novel. If you’ve ever set up a queue worker in production, you already know how to do this.

Where Reverb Gets Interesting: Horizontal Scaling

The natural concern with a single long-running WebSocket server is what happens when you need multiple application servers. WebSocket connections are stateful: each connection is pinned to a specific server process. If user A is connected to server 1 and a broadcast event goes out on server 2, the connection on server 1 won’t receive it.

Reverb handles this via Redis pub/sub. When an event broadcasts on any application server, it publishes to Redis. Reverb processes on all servers subscribe to the same Redis channels and push the message to their connected clients. Your messages reach every connected user regardless of which application server processed the request.

The configuration:

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

You’re probably already running Redis for cache or queues. Scaling Reverb to multiple nodes is an environment variable change and a config flag, not a new infrastructure dependency.

The connection limits that Pusher charges you to expand are now a function of your server capacity and how many Reverb instances you run, not a pricing tier on someone else’s platform.

What Reverb Cannot Do (Being Honest)

Reverb is not a drop-in replacement for every Pusher use case. There are real limitations worth knowing before you start.

No built-in presence channel analytics. Pusher’s dashboard shows concurrent connections, message throughput and channel breakdowns in real time. Reverb has no built-in dashboard. You get log output and whatever monitoring you wire up yourself. For most apps this isn’t a problem, but if your team is used to opening Pusher’s interface during incidents, you’ll need to build that visibility elsewhere.

No global edge network. Pusher runs WebSocket servers in multiple regions close to users. Reverb runs wherever your application runs. If your users are in Singapore and your server is in Frankfurt, that latency is yours to manage. A CDN doesn’t help with WebSockets. The fix is deploying Reverb in a region closer to your users, which is the same problem you’d solve for any latency-sensitive application.

No managed failover. If your Reverb process dies, Supervisor restarts it and connected clients reconnect via Laravel Echo’s built-in reconnection logic. For most applications this is acceptable. For something like a live financial trading dashboard where connection drops have direct user impact, you’d want a more hardened setup or a managed service.

Messages per second limits at high volume. Under very high message rates, a single Reverb process will hit throughput limits before a purpose-built service like Ably or Pusher would. If you’re pushing millions of messages per second, this is a conversation about dedicated infrastructure, not about which WebSocket driver you use.

Most applications don’t live near these edge cases. Know your actual traffic patterns before deciding they apply to you.

The Math, Run Honestly

Pusher’s free tier gives you 200 concurrent connections. That sounds generous until your app has a few hundred users online at the same time. At that point you’re on the Startup plan at $49/month, and the next tier up is $99/month for 2,000 concurrent connections.

Reverb on a Hetzner CX22 (4 GB RAM, 2 vCPU, roughly €8/month) will comfortably handle several thousand concurrent WebSocket connections. The process is lightweight: open connections hold a file descriptor and very little memory compared to traditional threaded servers.

If you’re already running a VPS for your application, Reverb runs on the same box at no additional cost. The only material cost is the Supervisor config and the Nginx proxy block. If you need dedicated capacity, a small €8/month server for the WebSocket process replaces a $49/month Pusher plan with room to spare.

The crossover point where Pusher becomes cheaper than self-hosting is very high. It would require either extremely high message volumes pushing you to Pusher’s upper tiers (where the savings from Reverb are even larger) or a team with zero operational capacity to manage a process entry.

When to Keep Pusher

This is a real answer, not a token balance.

Keep Pusher if your team has no one who can own infrastructure. If you’re a two-person product startup and your two people are both focused entirely on product and no one is going to notice or respond to a failed Supervisor process, paying Pusher is the correct decision. The operational overhead is real even if it’s small.

Keep Pusher if you’re already on a fully managed PaaS like Laravel Cloud or Heroku where running a persistent process is awkward or unsupported. Some hosting environments make long-running processes genuinely harder to manage.

Keep Pusher if your users are globally distributed and latency is a measurable product problem. You’d need to run Reverb in multiple regions to match Pusher’s edge coverage, which is a different kind of operational work.

Keep Ably or Pusher if you need guaranteed delivery semantics or sophisticated presence features that Reverb doesn’t implement. For a real-time multiplayer game or a collaborative editing tool, you may be using platform features that Reverb doesn’t offer.

For a standard SaaS with notifications, live dashboards, chat or real-time feed updates: Reverb is ready and Pusher is optional.

The Migration

For an existing Laravel app using Pusher, switching to Reverb takes about an hour. Here’s the actual sequence:

Install Reverb and update your broadcaster config. Set the new environment variables. Update your Nginx config to proxy WebSocket connections. Add the Supervisor program entry. Restart Supervisor. Test connections.

Your frontend Echo code doesn’t change. Your broadcast events don’t change. Your channel authorization doesn’t change. The only thing that changes is where the WebSocket server runs.

A few things to check during migration: make sure any PUSHER_APP_* environment variables are replaced with the Reverb equivalents, and verify that presence channels work correctly if you use them, as the authentication flow is identical but worth testing end-to-end before you drop the Pusher plan.

The Actual Question

Pusher is a good product. This isn’t about Pusher being bad. It’s about whether you’re paying for a service because it’s genuinely the right tool for your situation, or because you haven’t revisited that decision since the original developer set it up three years ago.

If you evaluated Reverb when it shipped in March 2024 and decided it wasn’t ready, that evaluation is two years old. The project has had dozens of releases since then, is maintained by the same team that maintains Laravel and is included in the official Laravel documentation as the recommended self-hosted broadcasting solution.

The question is simpler than the infrastructure: are you choosing Pusher, or are you just not thinking about it?

If it’s the latter, this week’s the week to check.

Found this helpful?

If this article saved you time or solved a problem, consider supporting — it helps keep the writing going.

Originally published on Medium.

View on Medium
Your Laravel App Is Still Paying Pusher for No Reason — Hafiq Iqmal — Hafiq Iqmal