Skip to content

centrifugo

v6.10.3
centrifugal/centrifugo

Scalable 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.

Version of centrifugal/centrifugo you run

CompareView on GitHub

Breaking changes

What to check before upgrading, taken from the notes of the last 30 releases.

  • v6.9.1Jul 13, 2026
  • v6.9.0Jul 7, 2026

    This release comes with ⚠️ BREAKING CHANGES ⚠️

    TLDR: Proxy http_headers is now transport-only; Need to configure explicit emulated_headers list for allowed emulated headers in Centrifugo server configuration.

    Before v6.9.0, the proxy http_headers option 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_headers no longer forwards emulated headers. To forward a header supplied via headers emulation, you must now list it in the new emulated_headers option near http_headers.

    Why we had to make this a breaking change. Before v6.9.0 a name listed in http_headers could be filled from either the real connection request or the client's emulation map. That made a trusted header like x-user-id forgeable in a way that is not the operator's mistake:

    • An operator adds x-user-id to http_headers for 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 supply x-user-id via emulation, especially if they have never heard of the headers emulation feature.
    • That reverse proxy might set x-user-id only for authenticated requests and leave it fully unset otherwise.
    • A client then sends x-user-id in 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 genuine x-user-id from 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_headers is incapable of carrying a client-emulated value. A name in http_headers is now sourced only from the real HTTP connection; anything a client sends via emulation must be explicitly opted into emulated_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 native dart:io these are sent as real WebSocket upgrade headers and are not affected)
    • using headers option in centrifuge-csharp
    • using unidirectional transports and passing headers as 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-Ip set by your reverse proxy or by native clients) — those keep working under http_headers with 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_headers proxy configuration object option near http_headers. Apply this change to every proxy that used emulated headers (connect, refresh, channel proxies, RPC proxies, and any named proxies in the top-level proxies list).

Release history

All releases on GitHub
BadgeLatest version of centrifugal/centrifugo[![version](/badge/centrifugal/centrifugo)](/p/centrifugal/centrifugo)

Centrifugo 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-file flag 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 logs shutdown timeout reached and 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_limit and history_max_publication_limit set the maximum number of publications returned in one recovery and in one history request for a namespace. Before, only client.recovery_max_publication_limit and client.history_max_publication_limit existed, 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 the client.* 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 available to 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_timeout passed (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, rustcent and csharpcent – 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.

Full release notesCompare with previous