Soketi
Self-hostable WebSocket server compatible with Pusher
Soketi is a fast, open-source WebSocket server that implements the Pusher protocol, making it a drop-in self-hosted replacement for Pusher Channels. It is built for low latency and simple deployment.
Key features
- Pusher protocol compatible
- Low latency
- Simple deployment
- Horizontal scaling
Pros & cons
Strengths
- Drop-in Pusher replacement
- Lightweight
Trade-offs
- Protocol-only, no UI
- Smaller ecosystem
Soketi replaces
Last reviewed Sep 13, 2026 · 789 words
Switching a Pusher Channels app to Soketi is a diff of about six lines in your config: the host, the port, the app ID, key and secret, and useTLS. The pusher-js client, Laravel Echo, the PHP and Node server SDKs and the HTTP API all keep working unmodified, because Soketi implements the Pusher protocol rather than inventing one. That is the entire product, and for a lot of apps it is exactly the right size: a real-time layer that costs 128 MB of RAM instead of a monthly bill that scales with concurrent connections.
Configuration is environment variables and nothing else
There is no UI and no config file worth speaking of. A first app is defined with three variables, and the server listens on port 6001 with Prometheus metrics on 9601:
services:
soketi:
image: quay.io/soketi/soketi:latest-16-alpine
environment:
- SOKETI_DEFAULT_APP_ID=app-id
- SOKETI_DEFAULT_APP_KEY=app-key
- SOKETI_DEFAULT_APP_SECRET=app-secret
- SOKETI_DEFAULT_APP_ENABLE_CLIENT_MESSAGES=false
- SOKETI_METRICS_ENABLED=1
ports:
- "6001:6001"
- "9601:9601"
restart: unless-stopped
On the application side, a Laravel .env becomes PUSHER_HOST=ws.example.com, PUSHER_PORT=443, PUSHER_SCHEME=https and the same three credentials. Put Soketi behind your reverse proxy with WebSocket upgrade enabled and TLS terminated there; running it without TLS on the public internet works, and browsers on an HTTPS page will refuse the plain ws:// connection, so you will do this anyway.
The DEFAULT_APP_ variables cover one app. For several, an array of apps goes in SOKETI_DEFAULT_APP_-style JSON or, better, a MySQL, PostgreSQL or DynamoDB app manager so apps are rows in a table you already run. Private and presence channels authenticate exactly as with Pusher: the client hits your app's /broadcasting/auth endpoint, your app signs with the secret, Soketi checks the signature.
Scaling is one Redis and a flag
A single Soketi process handles thousands of concurrent connections on a small VPS; the server is built on uWebSockets.js, which is where the "low latency" claim comes from. When one process is not enough, or you want zero-downtime restarts, run several behind the proxy and set SOKETI_ADAPTER_DRIVER=redis with SOKETI_DB_REDIS_HOST so a message published to one node reaches clients connected to another. The same Redis can hold rate-limit counters. Kubernetes users get a Helm chart and the horizontal story is the same: stateless pods, one Redis.
Rate limiting is per app: max_connections, max_backend_events_per_second and max_client_events_per_second are app attributes, and hitting them returns the same error codes Pusher does. Webhooks for channel_occupied, member_added and friends are also per app and post to your backend with the same payload shape.
The question to check before you commit
Soketi's commit activity slowed noticeably after 2023, and the maintainer has been open about limited time. The protocol is stable, the server works, and the AGPL-3.0 code is small enough to patch, but you are adopting something with a small ecosystem and slow releases, which the catalogue lists as its main con. Two things changed the calculus since it launched. Laravel now ships Reverb, a first-party WebSocket server for the same Echo clients, which is the obvious pick if your backend is Laravel and you have no other consumers. And Centrifugo has become the general-purpose self-hosted real-time server in the communication category, with its own protocol, an admin UI, and a much larger deployment base; it is the safer choice for a new project that is not already written against the Pusher API.
Soketi's niche is therefore narrow and real: an existing Pusher integration, especially in a non-Laravel stack or one with mobile clients using Pusher SDKs, where the goal is to stop paying Pusher with zero client rewrites.
Operational notes
Soketi keeps no persistent state, so there is nothing to back up beyond the app credentials. Monitor soketi_connected on the metrics port and alert on it dropping to zero. Set SOKETI_SHUTDOWN_GRACE_PERIOD so a restart drains connections instead of dropping them, and configure a client-side reconnect anyway, because a WebSocket layer that assumes it will never restart is lying to itself. AGPL applies if you modify Soketi and offer it as a service; running unmodified copies for your own app carries no obligation beyond the notice.
What I'd do
If you are migrating an app off Pusher this quarter, run Soketi: one container, three variables, Redis adapter once you need a second node, and an hour of testing presence channels. Pin the image tag rather than latest, because releases are rare and you want to know when one lands. If you are starting a new real-time feature from scratch, pick Reverb on Laravel or Centrifugo anywhere else and skip the Pusher protocol entirely.
Compare Soketi
2 head-to-head comparisons.
Similar chat & communication apps
Chatwoot
Chat & CommunicationOpen-source customer engagement and live-chat platform
Replaces Intercom, Zendesk
SimpleX Chat
Chat & CommunicationPrivate messenger without any user identifiers
Replaces Signal, WhatsApp
Etherpad
Chat & CommunicationReal-time collaborative document editor
Replaces Google Docs
Centrifugo
Chat & CommunicationScalable real-time messaging server for apps and websites
Replaces Pusher, Firebase Realtime Database
Screego
Chat & CommunicationScreen sharing for developers
Typebot
Chat & CommunicationConversational app builder (alternative to Typeform and Landbot)