We are excited to announce Centrifugo v6 – a new major release that is now live. This release includes fundamental improvements in the configuration to simplify working with Centrifugo from both user and core development perspectives. It also adds several useful features and enhances observability for both Centrifugo OSS and Centrifugo PRO.
Prefer podcast version of this post? (8.5 MB).
For those who never heard about Centrifugo before, it is a scalable real-time messaging server in a self-hosted environment. Centrifugo allows you to build real-time features in your application, such as chat, notifications, live updates, and more. It’s modern, fast, reliable and lightweight. Centrifugo is used by many companies worldwide to power real-time features in their applications. Find out more information in introduction.
In a recent blog post, we talked about notable Centrifugo v5 milestones. The v5 release was a significant milestone in the Centrifugo project’s history, introducing numerous new features and improvements. However, the time for a new major release had come.
Over the years, Centrifugo has evolved into a robust platform packed with numerous features. However, as Centrifugo’s capabilities expanded, so did the complexity of its configuration. Settings became increasingly dispersed across various parts of the codebase, making them harder to manage and understand. Adding new features required modifying more areas than it should have. With v6, we are addressing this challenge head-on by restructuring the configuration system and rethinking its organization.
Additionally, there were a few areas that required improvement but could not be addressed without breaking changes. A couple of deprecated features were removed in v6.
Let’s begin our dive into the Centrifugo v6 release with a description of the removed parts.
Removing SockJS
SockJS is a JavaScript library that provides a WebSocket-like object, but under the hood, it uses various transports to establish a connection (falling back to HTTP instead of WebSocket in case of connection issues).
The SockJS transport was deprecated in the Centrifugal ecosystem since the v4 release. We encouraged users to reach out if SockJS was still necessary in blog posts and marked it as deprecated in the documentation. However, nobody reached out during this time.
The SockJS client is poorly maintained these days, with issues not being addressed and some transports becoming outdated.
Since Centrifugo v4, we have our own WebSocket emulation layer. Unlike SockJS’s HTTP-based fallbacks, our layer:
- does not require sticky sessions in distributed cases (!),
- supports binary in the HTTP-streaming case,
- allows batching of messages in a more efficient wire format,
- is more performant in terms of CPU and memory on the server side, and
- requires fewer round-trips for connection establishment.
In Centrifugo v6, SockJS has been removed. Our JavaScript SDK, centrifuge-js
, will continue to support the SockJS transport for some time to work with Centrifugo v5, but we plan to remove it from the client SDK eventually.
To enable Centrifugo’s built-in bidirectional emulation, you need to enable HTTP streaming or SSE transports in the server configuration, and then configure centrifuge-js
to use those as described in its README:
const transports = [
{
transport: 'websocket',
endpoint: 'ws://localhost:8000/connection/websocket'
},
{
transport: 'http_stream',
endpoint: 'http://localhost:8000/connection/http_stream'
},
{
transport: 'sse',
endpoint: 'http://localhost:8000/connection/sse'
}
];
const centrifuge = new Centrifuge(transports);
centrifuge.connect()
Centrifugo v3 introduced the experimental Tarantool engine, and we were excited about it potentially being a solid alternative to Redis. However, since then, the Tarantool engine hasn’t seen many updates and is now missing some important features, like idempotent publishing and delta compression. These features were added to the memory and Redis engines during the v5 release, but unfortunately, Tarantool was left behind.
We were aware of only two setups where the Tarantool engine was used – and both clients eventually moved away from it with our help. Additionally, our usage statistics do not show any notable adoption of the Tarantool engine.
The reality is that while Tarantool provides some interesting technical advantages over Redis, maintaining proper integration with it and keeping it current is impossible given the resources of Centrifugal Labs. Furthermore, there was no significant support from the Centrifugo community to push its development forward.
For these reasons, we decided to remove Tarantool integration from Centrifugo. All Tarantool-related repositories will be moved to read-only mode. For now Centrifugal Labs will focus on Redis, Redis-compatible brokers, and NATS as the main scalability options for Centrifugo.
Sometimes, it’s necessary to drop some ballast to continue a beautiful journey…
Meanwhile, our Redis integration has been improved. In Centrifugo v4, we migrated to the redis/rueidis Go library, which enabled the Centrifugo node to achieve better Redis communication throughput. You can find more details in the blog post Improving Centrifugo Redis Engine throughput and allocation efficiency with Rueidis Go library.
We have also put more effort into integrating with Redis-compatible storages, such as DragonflyDB, which may unlock new and interesting capabilities without the need to maintain a separate engine.
Configuration refactoring
Over the years, Centrifugo’s configuration has been built on the approach initially established in its early versions. At the beginning, the number of configuration options was relatively small and manageable. However, with every new version and feature, the configuration became increasingly difficult to maintain and extend.
Refactoring this part is a challenging and not particularly enjoyable process, and it inevitably results in breaking compatibility. Nevertheless, for v6, we decided it was time to make this change.
Centrifugo v6 configuration has been completely restructured and now consists of distinct blocks – with all the options grouped together to clearly indicate the layer they correspond to.
For example, there is a client
top-level configuration block that contains options related to real-time client connections. To illustrate, let’s take the allowed_origins
option from Centrifugo v5:
Centrifugo v5 config example
{
"allowed_origins": ["https://example.com"]
}
It was moved under client
section in v6:
config.json
{
"client": {
"allowed_origins": ["https://example.com"]
}
}
It is now clear which layer of Centrifugo a given option corresponds to. For instance, the allowed_origins
option is related to client connections – not to the server API or the admin web interface.
Let’s look at another example. Remember that Centrifugo supports not only JSON configuration files but also YAML and TOML? Let’s look at another example, this time in YAML format:
Centrifugo v5 YAML config example
port: 8000
token_hmac_secret_key: XXX
admin_password: XXX
admin_secret: XXX
api_key: XXX
allowed_origins:
- http://localhost:3000
presence: true
namespaces:
- name: ns
presence: true
In v6 becomes:
config.yaml
http_server:
port: 8000
client:
token:
hmac_secret_key: XXX
allowed_origins:
- http://localhost:3000
admin:
password: XXX
secret: XXX
http_api:
key: XXX
channel:
without_namespace:
presence: true
namespaces:
- name: ns
presence: true
Again – we believe it’s much more clear now what part of server each option relates to.
We eliminated the situation where options were scattered across the Centrifugo codebase, often with unclear defaults and a non-obvious process for adding a new option. Now, the configuration is represented by a single Go struct. Config sections are organized into nested structs. Defaults are explicitly defined using struct field definition tags, making them easy to identify. This approach is simple to follow and extend – in most cases, adding a new option is as straightforward as introducing a new field in the struct or a nested struct for more complex functionality. Having all options centralized in a single struct also unlocks new possibilities for working with the configuration, as we will explore below.
One aspect we’d like to highlight is that channel options for channels without any namespace prefix are now defined under the channel -> without_namespace
block (as shown in the example above). This change ensures that options for channels without a namespace are not mixed with other Centrifugo options at the same level of configuration. Previously, the way namespace options were organized in the codebase led to several bugs – options for channels without a namespace required separate extraction, which was often overlooked. This issue has now been resolved. Additionally, with new config structure we more explicitly encourage users to adopt channel namespaces as a best practice when working with Centrifugo.
A great feature of Centrifugo is that it warns about unknown options in the configuration file and unknown environment variables at startup. This functionality, which helps users identify configuration mistakes, was already present in previous versions and remains in v6. Now, it also supports keys in deeply nested objects and arrays of objects without requiring excessive copy-paste in the codebase.
Re-structuring the configuration also affects how environment variables are constructed to configure Centrifugo. This will require users to update their environment variable configurations. To assist with this, we have added configuration converters to the v6 migration guide and introduced new CLI commands, which should greatly simplify the process (see more details below).
An important aspect of the new Centrifugo v6 configuration is that it uses the same TLS configuration object consistently across the entire system. Whenever you are configuring TLS now, you can expect the same field names, just at different configuration levels. TLS for the HTTP server, Redis client, NATS client, Kafka client, PostgreSQL client, and even mTLS support – all can be configured in a unified way.
The new TLS config object, which was already used in some places in v5, allows certs and keys to be passed in three different ways:
- as a string in the config with PEM-encoded cert/key content,
- as a base64 encoded string of PEM-encoded cert/key, or
- as a path to a file with PEM-encoded cert/key.
So you can choose the method that is most convenient for you.
Proxy config improvements
Due to its self-hosted nature, Centrifugo can provide its users an efficient way to proxy various connection events to the application backend, enabling the backend to respond in a customized manner to control the flow. This feature, called proxy is widely used by Centrifugo users. It allows authenticating connections in a customized manner (when the built-in JWT Centrifugo authentication is not suitable), managing channel subscription permissions and publication validations, refreshing client sessions, and handling RPC calls sent by a client over a bidirectional real-time connection.
In v6, there are a couple of notable improvements in the proxy feature configuration to make it simpler to use.
First, the separation between granular and non-granular proxy modes has been removed. In other words, there is no need to switch to a granular mode and reconfigure everything – proxies may be added gradually in the way which is more suitable for a real-time feature.
The connect
and refresh
proxies can now be enabled and configured at the client
level, while other types of proxies, which relate to channels, are configured within the channel
block and enabled at the channel namespace level. RPC proxy configuration is now defined under a separate rpc
section in the configuration.
It is now possible to define default proxies for all event types separately – connect
, refresh
, subscribe
, publish
, sub_refresh
, etc, – each with its own set of options. Previously, all proxies inherited the same set of options, and only endpoints and timeouts could be customized for specific proxy types. This new flexibility allows you to configure the desired proxy behavior without needing to use named proxy objects in many cases. For example, you can now define connect
and refresh
proxies with different sets of headers for each. While this behavior seems intuitive, it was previously only achievable by using the granular proxy mode and referencing custom proxies by name. Now, named proxy objects may be avoided in many cases where they were required before. So you can postpone using them until a more granular configuration is really required.
To simplify the process of creating a new configuration file or discovering available options, we added a new CLI command defaultconfig
.
The defaultconfig
command provides a way to get the configuration file with all defaults for all available configuration options. It will be possible using the command like:
centrifugo defaultconfig -c config.json
centrifugo defaultconfig -c config.yaml
centrifugo defaultconfig -c config.toml
Also, in dry-run mode it will be posted to STDOUT instead of file:
centrifugo defaultconfig -c config.json --dry-run
Finally, it’s possible to provide this command a base configuration file – so the result will inherit option values from base file and will extend it with defaults for everything else:
centrifugo defaultconfig -c config.json --dry-run --base existing_config.json
defaultenv cli helper
In addition to defaultconfig
added defaultenv
command which prints all config options as environment vars with default values to STDOUT:
$ centrifugo defaultenv
CENTRIFUGO_HTTP_SERVER_ADDRESS=""
CENTRIFUGO_HTTP_SERVER_PORT="8000"
CENTRIFUGO_ADMIN_ENABLED=false
CENTRIFUGO_ADMIN_EXTERNAL=false
CENTRIFUGO_ADMIN_HANDLER_PREFIX=""
CENTRIFUGO_ADMIN_INSECURE=false
CENTRIFUGO_ADMIN_PASSWORD=""
CENTRIF