nsd7 / surge
Surge β a lightweight, self-hosted, Pusher-compatible realtime broadcasting server for Laravel. No required dependencies.
Requires
- php: >=8.1
- ext-json: *
- ext-sockets: *
Requires (Dev)
None
Suggests
- ext-openswoole: Enables the high-performance multi-core Turbo runtime.
- ext-redis: Enables the Redis scaling adapter for running Surge across multiple nodes.
- illuminate/support: Required only to use the Laravel integration (service provider and surge:serve command).
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-11 09:44:37 UTC
README
A lightweight, self-hosted, Pusher-compatible realtime server for Laravel.
Point Laravel Echo at your own server β no code changes, no per-message bills.
π Documentation Β· Quickstart Β· Laravel Β· How it works Β· Roadmap
Surge speaks the Pusher WebSocket protocol, so it drops straight into an app that already uses Laravel broadcasting and Laravel Echo. Change the host and port in your config and your existing private/presence channels, events, and client events keep working β now on infrastructure you own.
The core has zero required dependencies: the WebSocket layer, the Pusher protocol, the HTTP trigger API, and even the Redis scaling client are written against PHP's standard library.
Why
- Drop-in. Pusher-protocol compatible. Keep Laravel Echo, keep your channels,
keep your
broadcast()calls. Just repoint them. - Self-hosted. No usage caps, no per-message pricing, no third-party seeing your traffic.
- Light. The default lite runtime is pure PHP β one file to run, nothing to compile, runs anywhere PHP runs.
- Honest. Fails closed on malformed frames, verifies every signature in constant time, and never claims a capability it doesn't have (see Status).
Quickstart
composer require nsd7/surge
Run the server (Pusher-compatible mode is the default):
SURGE_APP_ID=my-app SURGE_APP_KEY=my-key SURGE_APP_SECRET=my-secret \ vendor/bin/surge serve --port=9019
Connect from the browser with a plain WebSocket, or β more usefully β with Laravel Echo (below).
Use it with Laravel
Surge is a Laravel package: the service provider auto-registers and adds a
surge:serve command. Publish the config if you want to tweak it:
php artisan vendor:publish --tag=surge-config
1. Point the broadcaster at Surge in config/broadcasting.php (the built-in
pusher driver β no custom driver needed):
'pusher' => [ 'driver' => 'pusher', 'key' => env('SURGE_APP_KEY'), 'secret' => env('SURGE_APP_SECRET'), 'app_id' => env('SURGE_APP_ID'), 'options' => [ 'host' => '127.0.0.1', 'port' => 9019, 'scheme' => 'http', 'useTLS' => false, ], ],
2. Point Echo at Surge in resources/js/echo.js:
import Echo from 'laravel-echo'; import Pusher from 'pusher-js'; window.Pusher = Pusher; window.Echo = new Echo({ broadcaster: 'pusher', key: import.meta.env.VITE_SURGE_APP_KEY, wsHost: '127.0.0.1', wsPort: 9019, forceTLS: false, enabledTransports: ['ws'], });
3. Run the server and broadcast as usual:
php artisan surge:serve
broadcast(new OrderShipped($order)); // delivered over Surge
Private and presence channels authorize through your normal
routes/channels.php + /broadcasting/auth endpoint β Surge verifies the same
HMAC signature Laravel generates.
How it works
Surge is layered so the socket loop, the protocol, and the transport are independent:
ββββββββββββββββββββββββββββββββββββββββββββββββ
HTTP ββββΆβ Pusher HTTP API β Pusher WS protocol ββββ WebSocket
trigger β POST /apps/../events subscribe Β· presence Β· β (Laravel Echo)
β client events β
βββββββββββββββββ¬βββββββββββββββ¬ββββββββββββββββββ
β β
βββββββββ΄βββββββ ββββββ΄ββββββββββββββββ
β Runtime (WS) β β Scaling (optional) β
β lite: pure β β Redis pub/sub over β
β PHP select β β raw RESP sockets β
ββββββββββββββββ ββββββββββββββββββββββ
- RFC 6455 core β buffered frame parser (partial reads, batched frames, 16/64-bit lengths, masking), fragmented-message reassembly, ping/pong, close handling, and send backpressure. Rejects unmasked, oversized, or malformed frames instead of mangling them.
- Pusher protocol β
connection_established, public/private/presence channels,pusher:ping/pong, andclient-*events. - HTTP trigger API β
POST /apps/{id}/eventson the same port, with Pusher REST signature verification matchingpusher-php-server. - Scaling β optional Redis pub/sub so multiple nodes relay events to each other.
Configuration
Everything is env-driven (see config/surge.php):
| Variable | Default | Purpose |
|---|---|---|
SURGE_HOST / SURGE_PORT |
0.0.0.0 / 9019 |
Bind address |
SURGE_APP_ID / _KEY / _SECRET |
β | App credentials (match broadcasting + Echo) |
SURGE_ACTIVITY_TIMEOUT |
120 |
Seconds before a client is asked to ping |
SURGE_CLIENT_EVENTS |
true |
Allow client-* events on private/presence |
SURGE_SCALING_ENABLED |
false |
Turn on Redis pub/sub scaling |
Testing & benchmarks
The library ships a zero-dependency test runner β no PHPUnit needed:
php tests/run.php # 50 tests php bench/throughput.php # local fan-out sanity check
The benchmark reliably fans events out to 100 concurrent clients on a single machine; it's a correctness/sanity harness, not a tuned datacenter benchmark (real throughput depends on your hardware and the test harness is itself single-threaded).
Status
Surge is young. What's real today, and what isn't:
Working & tested
- RFC 6455 WebSocket server (lite, pure-PHP runtime)
- Pusher protocol: public/private/presence channels, client events, ping/pong
- Pusher HTTP trigger API with REST signature verification
- Laravel package (service provider, config,
surge:serve) - Redis pub/sub event relay across nodes
Honest caveats
- The pure-PHP lite runtime is single-process (one core). It is light, not the fastest. A multi-core turbo runtime (OpenSwoole) is included but experimental and not yet load-tested β see docs/turbo.md.
- Cross-node presence is supported but eventually consistent β a new node's first roster converges over one relay round-trip (member events still fire). Single-node presence is immediate.
- There is no TLS termination built in β run Surge behind a TLS-terminating
proxy (nginx, Caddy) for
wss://.
Roadmap
- Phase 0 β Correct RFC 6455 core behind a runtime interface
- Phase 1 β Pusher protocol (channels, presence, client events, auth)
- Phase 2 β Pusher HTTP trigger API (same port)
- Phase 3 β Laravel package (
surge:serve, config, Echo compatibility) - Phase 4 β Redis pub/sub horizontal scaling
- Phase 5 β README, examples, CI, integration + benchmark harness
- Observability β Prometheus
/metricsendpoint + structured JSON logging - [~] Multi-core turbo runtime (OpenSwoole) β code shipped, experimental, pending load-testing & benchmarks (docs/turbo.md)
- Shared cross-node presence (eventually consistent across nodes)
- TLS: by design, via a reverse proxy (nginx/Caddy) β see the docs
License
MIT Β© nowshad7