centrifugo
v6.10.3Scalable real-time messaging server in a language-agnostic way. Self-hosted alternative to Pubnub, Pusher, Ably, socket.io, Phoenix.PubSub, SignalR. Set up once and forever.
- Maintenance
- ActiveBased on the last push: commit history is not collected yet
- License
- Apache-2.0Permissive · OSI-approved
- Security
- 11 advisories2 critical, 3 high, 3 medium, 3 low · Scorecard 5.6/10
- Supported versions
- No scheduleendoflife.date does not track this project
11k stars · 754 forks · 35 open issues · last release 6 days ago · data checked 5 hours ago
Breaking changes
What to check before upgrading, taken from the notes of the last 30 releases.
- v6.9.1Jul 13, 2026
- ⚠️ Reminder: Centrifugo v6.9.0 had breaking changes in server configuration.
- v6.9.0Jul 7, 2026
This release comes with ⚠️ BREAKING CHANGES ⚠️
TLDR: Proxy
http_headersis now transport-only; Need to configure explicitemulated_headerslist for allowed emulated headers in Centrifugo server configuration.Before v6.9.0, the proxy
http_headersoption forwarded headers from two different sources:- transport-level headers — set on the real connection request (by the client transport or a reverse proxy/gateway in front of Centrifugo);
- client-supplied emulated headers — sent by the client inside its connect frame via the headers emulation feature.
In v6.9.0 these two sources are split into separate options:
| Option | Source | Trust |
|--------------------|-----------------------------------------|--------------------------------------------------------------|
|
http_headers| the real connection request only | controllable by your edge (a proxy/gateway can set/strip it) ||
emulated_headers| the client's headers emulation map only | always client-controlled — always an untrusted input |http_headersno longer forwards emulated headers. To forward a header supplied via headers emulation, you must now list it in the newemulated_headersoption nearhttp_headers.Why we had to make this a breaking change. Before v6.9.0 a name listed in
http_headerscould be filled from either the real connection request or the client's emulation map. That made a trusted header likex-user-idforgeable in a way that is not the operator's mistake:- An operator adds
x-user-idtohttp_headersfor a legitimate reason — their reverse proxy sets it, and they want that identity forwarded to the backend. They have no reason to expect this also lets a client supplyx-user-idvia emulation, especially if they have never heard of the headers emulation feature. - That reverse proxy might set
x-user-idonly for authenticated requests and leave it fully unset otherwise. - A client then sends
x-user-idin its connect frame via emulation. For any request where the proxy did not set the real header, Centrifugo forwarded the client's forged value to the backend, which trusted it as an authenticated identity.
A setup where the proxy always sets
x-user-id(for example to an empty string even for anonymous requests) is *not* vulnerable. But Centrifugo cannot inspect your proxy to know whether you did that, and cannot tell a genuinex-user-idfrom a client-emulated one at request time — so there is no way for Centrifugo to make this safe automatically or to warn only the affected setups.The only robust fix is structural to make operators aware of the feature: split the sources so
http_headersis incapable of carrying a client-emulated value. A name inhttp_headersis now sourced only from the real HTTP connection; anything a client sends via emulation must be explicitly opted intoemulated_headers(and is clearly documented as untrusted input). This removes the possibility of the flaw entirely, rather than relying on every operator to configure their proxy defensively against a feature they may not even know exists.Are you affected? Only if you rely on headers emulation:
- using headers option in
centrifuge-js - using headers option in
centrifuge-dart(web platform only — on nativedart:iothese are sent as real WebSocket upgrade headers and are not affected) - using headers option in
centrifuge-csharp - using unidirectional transports and passing
headersas part of ConnectRequest
You are not affected if you are not using headers at all or only forward real transport headers (e.g.
Cookie,Authorization,X-Real-Ipset by your reverse proxy or by native clients) — those keep working underhttp_headerswith no change.How to migrate: Before upgrading to v6.9.0, add the header names you allow to be sent over headers emulation to
emulated_headersproxy configuration object option nearhttp_headers. Apply this change to every proxy that used emulated headers (connect,refresh, channel proxies, RPC proxies, and any named proxies in the top-levelproxieslist).
Release history
All releases on GitHubCentrifugo is an open-source scalable real-time messaging server. It instantly delivers messages to application online users connected over supported transports (WebSocket, HTTP-streaming, Server-Sent Events (SSE), GRPC, WebTransport). Centrifugo is built around channel subscriptions – so it's a user-facing PUB/SUB server.
Centrifugo is language-agnostic and can be used to build chat apps, live comments, multiplayer games, real-time data visualizations, collaborative tools, AI streaming responses, etc. in combination with any backend. It is well suited for modern architectures and allows decoupling the business logic from the real-time transport layer.
Official client SDKs are available for JavaScript (browser, Node.js, React Native), Dart/Flutter, Swift, Java, Python, Go, Rust, and .NET. In addition, Centrifugo supports a unidirectional approach for simple use cases with no SDK dependency.
For details, go to the Centrifugo documentation site. For runnable demos see centrifugal/examples.
What's changed
Improvements
- New
--watch-config-fileflag restarts Centrifugo when the configuration in its config file changes – for development. All options apply, unlike with SIGHUP reload. A change which fails validation is logged and ignored, and Centrifugo keeps running. The restart happens in the same process, which keeps its PID. See restart on config file change (#502, #1302). - Graceful shutdown now covers everything Centrifugo runs (#1302). See signal handling:
- async consumers – Kafka, Redis Streams, NATS JetStream, PostgreSQL outbox, AWS SQS, Google Cloud PUB/SUB and Azure Service Bus – stop taking new messages, then finish and acknowledge the messages they already took. A message processed right before a stop is no longer delivered again after the restart;
- connections to Redis, PostgreSQL, NATS, proxy endpoints and JWKS endpoints are closed, and buffered OpenTelemetry spans are exported;
- the whole shutdown finishes within
shutdown.timeout. If it doesn't, Centrifugo logsshutdown timeout reachedand exits with code 1, as before; - when a server, an async consumer or another component fails after start, Centrifugo shuts down gracefully before exiting with code 1, instead of exiting at once;
- startup errors such as a port in use or a bad TLS configuration are reported before Centrifugo starts processing messages from async consumers.
- New channel options
recovery_max_publication_limitandhistory_max_publication_limitset the maximum number of publications returned in one recovery and in one history request for a namespace. Before, onlyclient.recovery_max_publication_limitandclient.history_max_publication_limitexisted, applying to all channels. A namespace whose history holds many small publications – for example, AI responses streamed in chunks – can now have a higher limit than the rest. Zero, the default, keeps using theclient.*options. See channel options and Centrifugo for AI apps (#1294, centrifugal/centrifuge#674). - Better comments in Protobuf definitions for server API and Proxy.
Fixes
- SIGHUP reload pointing channel or RPC options to a proxy which was not created on start was reported as successful, and the next operation using that proxy panicked. Such a reload is now rejected with an error, and the previous configuration stays active – a restart is required to start using a new proxy. Note: enabling a proxy kind for which no proxy was created on start used to reload successfully and then return
108 not availableto clients, and is now rejected too. See signal handling (#1300). - Redis Streams consumer: messages read from Redis right before shutdown could be left unprocessed, and processed only once
visibility_timeoutpassed (30 seconds by default) (#1307). - A server-side subscribe, refresh or message sent with the server API to a client which was still connecting could reach the client before its connect reply. They now come after it (centrifugal/centrifuge#676).
- Other subscribers of a channel could get a leave event without a join event for a client whose server-side subscribe happened while its message queue was full (centrifugal/centrifuge#673).
Miscellaneous
- New Server SDKs page in the docs lists the official server SDKs –
gocent,pycent,jscent,phpcent,rubycent,rustcentandcsharpcent– with install lines, a first publication in each language, notes for AI coding agents, and when to use GRPC instead of the HTTP API. All server SDKs follow a common SDK spec and include not only Centrifugo HTTP API client, but also helper methods for connection/subscription token generation, shared poll signatures, helpers functions to implement proxy handlers. - Introducing centrifuge-rust – a new official client SDK, with Tauri and WASM support out of the box.
- Centrifugo for AI apps now covers checkpoints for long responses, loading a conversation with
getState, and stopping generation from every device. See the new Vercel AI SDK chat example. - Web UI is updated to the latest dependencies (#1305).
- This release is built with Go 1.27.3.
- Dependency updates.
- See also the corresponding Centrifugo PRO release.