projectsaturnstudios / forte
Drop-in replacement for Laravel Reverb on an epoll/kqueue event loop.
Requires
- php: ^8.4
- guzzlehttp/psr7: ^2.6|^3.0
- illuminate/console: ^11.0|^12.0|^13.0
- illuminate/contracts: ^11.0|^12.0|^13.0
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- laravel/prompts: ^0.1.15|^0.2.0|^0.3.0
- pusher/pusher-php-server: ^7.2
- ratchet/rfc6455: ^0.4
- symfony/console: ^6.0|^7.0|^8.0
- symfony/http-foundation: ^6.3|^7.0|^8.0
- symfony/routing: ^6.3|^7.0|^8.0
Requires (Dev)
- jasonmccreary/double: ^1.0
- laravel/pulse: ^1.8
- orchestra/testbench: ^9.15|^10.8|^11.0
- pestphp/pest: ^4.0
Suggests
- ext-epoll: Linux: pie install php-io-extensions/epoll. Forte falls back to stream_select without it.
- ext-kqueue: macOS: pie install php-io-extensions/kqueue. Forte falls back to stream_select without it.
Provides
None
Conflicts
None
Replaces
None
README
ProjectSaturn Forte
A literal drop-in replacement for Laravel Reverb
Laravel Reverb is a great WebSocket server. It speaks the Pusher protocol, so Echo and every Pusher client already know how to talk to it, and it lives inside your Laravel app. Under the hood it runs on ReactPHP, and ReactPHP's event loop asks the operating system "which of my sockets can I read or write right now?" through stream_select() unless you install ext-uv, ev or event. stream_select() scans every socket on every wakeup and stops at FD_SETSIZE, usually 1024. That's why Reverb's docs tell you to install ext-uv before you go past a thousand connections.
Linux has had the better answer for years: epoll. macOS has kqueue. You register each socket with the kernel once and it hands back only the ones with activity. PHP just never exposed either one to userland, so I wrote ext-epoll and ext-kqueue: the syscalls, one to one, no opinions.
Forte is Reverb with ReactPHP taken out and those extensions put in. Just replace reverb by installing this package, and making a tiny set of changes to migrate and thats it.
Nothing in ReactPHP sits on epoll or kqueue directly, so Forte reimplements the part ReactPHP was doing: the event loop, the TCP and TLS server, the HTTP layer the Pusher API rides on, the Redis client for scaling, and the promises that tie them together. Everything above that is Reverb's own code, forked at 74c8c40 under its MIT license: channels, presence, the Pusher protocol, the HTTP API, Pulse cards, rate limiting. Your .env keeps its REVERB_* keys, Laravel keeps its reverb broadcasting driver, Echo keeps its config. You swap the package and the start command.
No extension yet? Forte falls back to stream_select(), so you can deploy first and install the extension whenever you get to it.
Benchmarks
Forte outperformed Reverb on every server metric we measured, on the box most Reverb servers actually run on: one vCPU, shared with the staging sites already living there. Forte on ext-epoll against Reverb on ext-uv at 950 connections:
- 67% more broadcasts per second, every event delivered to every client.
- 51% less CPU per delivered message.
- 41% less memory per connection.
- 24% lower median and 21% lower p99 latency at the same publish rate.
At 4,900 connections the lead holds, with 49% more throughput and 45% less memory per connection. With no extension at all, Forte on stream_select() beats Reverb on stream_select() too.
Don't actually trust me, bro. The science is in BENCHMARKS.md: the box, every version, the method, every table, the raw data, the harness to rerun it, and the numbers that went the other way.
Installation
| PHP | 8.4 or newer. |
| Laravel | 11, 12 or 13. |
| Loop extension | Optional, recommended. ext-epoll on Linux, ext-kqueue on macOS. Without one, Forte runs on stream_select() and its 1024-descriptor ceiling. |
| Redis | Only for horizontal scaling, same as Reverb. |
composer require projectsaturnstudios/forte
Then install the loop extension on the machine that runs forte:start, with PIE:
sudo pie install php-io-extensions/epoll # Linux pie install php-io-extensions/kqueue # macOS
Forte picks the loop when the server starts: epoll if it's loaded, then kqueue, then stream_select(). The start line tells you which one you got:
$ php artisan forte:start INFO Starting server on 0.0.0.0:8080 (epoll).
On stream_select() it also prints a warning naming the extension to install.
How to use
Migrating from Reverb
composer remove laravel/reverb composer require projectsaturnstudios/forte
Then, wherever your process manager runs Reverb (Supervisor, Forge daemon, systemd), change the command:
| Reverb | Forte |
|---|---|
php artisan reverb:start |
php artisan forte:start |
php artisan reverb:restart |
php artisan forte:restart |
forte:start takes Reverb's flags, --host, --port, --path, --hostname and --debug, plus --loop=epoll|kqueue|select to force a driver.
That's the migration. What stays as it is:
.env: Forte reads everyREVERB_*key Reverb reads. AFORTE_*key of the same name wins when both are set, andFORTE_LOOPforces the loop driver.config/reverb.php: if your app published it, Forte runs on it, custom apps and allowed origins included. Publish Forte's own file withphp artisan vendor:publish --tag=forte-configwhen you're ready, and from then onconfig/forte.phpis the one that counts.config/broadcasting.php: thereverbconnection is Laravel's, so it doesn't change. Your app still broadcasts withBROADCAST_CONNECTION=reverb.- Echo and
VITE_REVERB_*: same protocol, same host, same port, same keys. No front-end rebuild. - Scaling:
REVERB_SCALING_ENABLED,REVERB_SCALING_CHANNELand yourREDIS_*settings work as they did. Switch every node in one deploy: each node serializes its ownApplicationclass into the Redis messages, so a Reverb node and a Forte node can't read each other's. - Pulse: the recorder classes Reverb's docs put in
config/pulse.php(Laravel\Reverb\Pulse\Recorders\ReverbConnections,ReverbMessages) resolve to Forte's recorders, and thereverb.connectionsandreverb.messagescards still render.
Fresh Install
composer require projectsaturnstudios/forte php artisan forte:install
forte:install does what reverb:install does. It writes the REVERB_* and VITE_REVERB_* keys into .env with a generated app id, key and secret, publishes config/forte.php, adds the reverb connection to config/broadcasting.php, offers to enable broadcasting (install:broadcasting without pulling in laravel/reverb), and offers to switch BROADCAST_CONNECTION to reverb. Last, it tells you which loop extension to install if this machine doesn't have it.
Then start the server:
php artisan forte:start
From here everything in Laravel's broadcasting docs and Reverb's docs applies: events that implement ShouldBroadcast, channel authorization in routes/channels.php, Echo on the client. Running it in production is the same too, with forte:start in place of reverb:start: one process under a supervisor, raise the open-file limit (ulimit -n) for the connections you expect, and put Nginx in front for TLS, or let --hostname pick up a Herd or Valet certificate locally.
AI-assisted development
This codebase was built with LLM assistance under human direction. Prompts, reviews, and architectural decisions were owned by a professional software engineer — the model accelerates drafting and exploration; it does not replace judgment on APIs, dependency direction, or what ships.
Agentic Documentation
This repository carries an Open Knowledge Format bundle at .okf/: Markdown with YAML frontmatter, readable by you and loadable by a coding agent. It records what the README doesn't: which parts of the tree are Reverb's and which are Forte's, how the loop picks and drives epoll, kqueue or stream_select(), how a connection moves from TCP accept through TLS and the WebSocket handshake, what scaling sends over Redis, how a Reverb app's .env and config keep working, and a log of every change that altered behaviour.
Start at .okf/index.md. AGENTS.md is the short rule sheet an agent reads before touching the transport, the forked Reverb code, or the config; each rule points into the bundle for the reason behind it.
Point your agent at the repo and it has the same context the maintainer has. Point yourself at it when the README's answer is "it does X" and you want "because".
Resources
- Laravel Reverb and Broadcasting: the docs for everything Forte keeps. Where they say
reverb:start, readforte:start. - laravel/reverb: the upstream Forte forks from.
- ext-epoll and ext-kqueue: the extensions Forte's loop runs on.
- Pusher Channels protocol and HTTP API: the wire format Forte serves.
- Open Knowledge Format: the spec the
.okf/bundle follows.
Running the test suite
The suite is Pest v4: Reverb's own tests, moved onto Forte's loop, plus Forte's tests for the loop drivers, the TLS server and the Redis client. The feature tests start a real server on 0.0.0.0:8080 and talk to it with a WebSocket client and an HTTP client that also run on Forte's loop, so nothing else may be listening on 8080. The redis group needs Redis on 127.0.0.1:6379.
git clone https://github.com/projectsaturnstudios/forte.git
cd forte
composer install
vendor/bin/pest
Tests: 9 skipped, 388 passed (1074 assertions)
That run is Linux with ext-epoll. Skips are the other platform's driver and two tests that need ulimit -n above 1024. Run it once per driver you have:
FORTE_LOOP=select vendor/bin/pest FORTE_LOOP=epoll vendor/bin/pest # Linux, ext-epoll loaded FORTE_LOOP=kqueue vendor/bin/pest # macOS, ext-kqueue loaded vendor/bin/pest --exclude-group=redis # no Redis on this machine
CI runs the suite on all three drivers with PHP 8.4 and 8.5, NTS and ZTS, and the Autobahn WebSocket compliance suite against the epoll and select loops.
Contributing
Open an issue first for anything beyond a typo. Pull requests come with a Pest test for the behaviour and keep CI green on every driver. Code under src/Servers/Forte/ and src/Promise/ is Forte's; everything else is Reverb's, and changes there stay as close to upstream as the fix allows so upstream fixes keep porting cleanly. Forte depends on no ReactPHP package, dev dependencies included. Read AGENTS.md for the rules the code follows.
Security
Forte is a network server: it accepts connections, terminates TLS when you give it a certificate, and checks Pusher signatures on every HTTP API call. See SECURITY.md for the model and how to report a vulnerability.
License
MIT. See LICENSE. Forte is a fork of Laravel Reverb by Taylor Otwell, Joe Dixon and the Laravel contributors, under the same license.