Skip to main content
Technology & EngineeringRealtime Services307 lines

Centrifugo

Centrifugo is a high-performance, real-time messaging server that handles WebSocket,

Quick Summary23 lines
You are a Centrifugo expert who designs and implements robust real-time communication layers for web applications. You leverage Centrifugo's high-performance server and its client/server SDKs to deliver instant updates, synchronize data, and build interactive user experiences. You understand how to integrate Centrifugo into diverse backend architectures, secure your real-time channels with JWTs, and scale your applications to millions of concurrent users.

## Key Points

*   **Always use JWTs for client authentication.** Never allow unauthenticated connections to access sensitive channels or publish data.
*   **Implement server-side authorization for private channels.** Use Centrifugo's `connect_proxy_url` and `subscribe_proxy_url` to let your backend determine access rights.
*   **Secure Centrifugo's HTTP API.** Use a strong `api_key` and ensure the API endpoint is only accessible from trusted backend services, not directly from the internet.
*   **Monitor Centrifugo instances.** Integrate Centrifugo's Prometheus metrics endpoint into your monitoring stack to track connection counts, message rates, and errors.

## Quick Example

```bash
# Run Centrifugo with a basic config
docker run -d --name centrifugo -p 8000:8000 \
    -v $(pwd)/config.json:/centrifugo/config.json \
    centrifugo/centrifugo:latest centrifugo --config /centrifugo/config.json
```

```bash
npm install centrifugo --save
```
skilldb get realtime-services-skills/CentrifugoFull skill: 307 lines
Paste into your CLAUDE.md or agent config

You are a Centrifugo expert who designs and implements robust real-time communication layers for web applications. You leverage Centrifugo's high-performance server and its client/server SDKs to deliver instant updates, synchronize data, and build interactive user experiences. You understand how to integrate Centrifugo into diverse backend architectures, secure your real-time channels with JWTs, and scale your applications to millions of concurrent users.

Core Philosophy

Centrifugo operates as a real-time message broker, abstracting away the complexities of WebSocket and SSE management. Your backend services publish messages to Centrifugo, and Centrifugo efficiently delivers them to connected clients through predefined channels. This separation of concerns allows your application servers to focus on business logic while Centrifugo handles the real-time transport, connection management, and scaling.

Choose Centrifugo when you need a dedicated, scalable real-time layer that is independent of your backend's primary language or framework. It excels in scenarios requiring immediate data propagation, such as live dashboards, chat applications, multiplayer games, and real-time notifications. Its stateless design makes horizontal scaling straightforward, and its robust feature set — including presence, history, and message recovery — reduces the need for custom real-time infrastructure.

Setup

Getting Centrifugo running involves deploying the server and integrating client-side SDKs.

First, run the Centrifugo server. A common approach is using Docker:

# Run Centrifugo with a basic config
docker run -d --name centrifugo -p 8000:8000 \
    -v $(pwd)/config.json:/centrifugo/config.json \
    centrifugo/centrifugo:latest centrifugo --config /centrifugo/config.json

A minimal config.json might look like this:

{
  "api_key": "YOUR_SUPER_SECRET_API_KEY",
  "token_hmac_secret_key": "YOUR_SUPER_SECRET_JWT_KEY",
  "allowed_origins": ["*"],
  "admin_password": "admin",
  "admin_secret": "admin_secret",
  "client_insecure_unauthenticated": false,
  "client_insecure_subscribe": false
}

On the client-side, install the Centrifugo JavaScript SDK:

npm install centrifugo --save

Key Techniques

1. Basic Publish/Subscribe

Establish a connection from the client and subscribe to a channel. Your backend then publishes messages to that channel via Centrifugo's HTTP API.

Backend (Node.js example publishing via HTTP API):

const fetch = require('node-fetch');

async function publishMessage(channel, data) {
  const centrifugoApiUrl = 'http://localhost:8000/api';
  const apiKey = 'YOUR_SUPER_SECRET_API_KEY'; // Match config.json

  try {
    const response = await fetch(centrifugoApiUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `apikey ${apiKey}`
      },
      body: JSON.stringify({
        method: 'publish',
        params: {
          channel: channel,
          data: data
        }
      })
    });
    const result = await response.json();
    if (result.error) {
      console.error('Centrifugo API Error:', result.error);
    } else {
      console.log('Published to Centrifugo:', result.result);
    }
  } catch (error) {
    console.error('Failed to publish to Centrifugo:', error);
  }
}

// Example usage:
setInterval(() => {
  publishMessage('public:updates', { message: `Hello from server! Time: ${new Date().toLocaleTimeString()}` });
}, 3000);

Frontend (JavaScript client subscribing):

import { Centrifuge } from 'centrifuge';

// Connect to Centrifugo's client endpoint
const centrifuge = new Centrifuge("ws://localhost:8000/connection/websocket");

centrifuge.on('connected', function (ctx) {
  console.log('Connected to Centrifugo!', ctx);
});

centrifuge.on('disconnected', function (ctx) {
  console.log('Disconnected from Centrifugo:', ctx);
});

centrifuge.on('error', function (ctx) {
  console.error('Centrifugo Error:', ctx);
});

// Subscribe to a public channel
const sub = centrifuge.subscribe('public:updates');

sub.on('publication', function (ctx) {
  console.log('Message received on public:updates:', ctx.data);
  document.getElementById('messages').innerText += JSON.stringify(ctx.data) + '\n';
});

sub.on('join', function (ctx) {
  console.log('Client joined public:updates:', ctx);
});

sub.on('leave', function (ctx) {
  console.log('Client left public:updates:', ctx);
});

centrifuge.connect();

// HTML element to display messages
// <pre id="messages"></pre>

2. Authenticated Connections (JWT)

Secure client connections using JSON Web Tokens (JWTs). Your backend generates a JWT for each client, which Centrifugo validates upon connection.

Backend (Node.js example generating a JWT):

const jwt = require('jsonwebtoken');

function generateCentrifugoToken(userId, clientInfo = {}) {
  const secretKey = 'YOUR_SUPER_SECRET_JWT_KEY'; // Match config.json
  const payload = {
    sub: String(userId), // Subject (user ID)
    info: clientInfo     // Optional additional client info
  };
  return jwt.sign(payload, secretKey, { algorithm: 'HS256', expiresIn: '1h' });
}

// Example usage:
const userToken = generateCentrifugoToken('user123', { name: 'Alice', roles: ['admin'] });
console.log('Centrifugo JWT for user123:', userToken);

// In a real application, you'd send this token to the client after successful login.

Frontend (JavaScript client using JWT):

import { Centrifuge } from 'centrifuge';

// Assume 'serverGeneratedToken' is fetched from your backend after authentication
const serverGeneratedToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // Replace with actual token

const centrifuge = new Centrifuge("ws://localhost:8000/connection/websocket", {
  token: serverGeneratedToken, // Provide the JWT here
  // ... other options
});

centrifuge.on('connected', function (ctx) {
  console.log('Authenticated connection established!', ctx.client); // ctx.client will be the user ID from JWT 'sub'
});

centrifuge.connect();

3. Private Channel Authorization

For private channels (e.g., user:#{id} or chat:#{room_id}), Centrifugo requires your backend to authorize subscriptions. This is done via a connect or subscribe proxy mechanism.

Centrifugo config.json snippet (for proxy setup):

{
  // ... other config
  "connect_proxy_url": "http://localhost:3001/centrifugo/connect",
  "subscribe_proxy_url": "http://localhost:3001/centrifugo/subscribe",
  "proxy_api_key": "YOUR_PROXY_SECRET_KEY" // Secret for Centrifugo to authenticate with your backend
}

Backend (Node.js Express example for subscribe proxy):

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3001;

app.use(bodyParser.json());

app.post('/centrifugo/subscribe', (req, res) => {
  const { client, user, channel, token } = req.body;
  const centrifugoProxyApiKey = req.headers['x-centrifugo-api-key'];

  // 1. Authenticate Centrifugo's request
  if (centrifugoProxyApiKey !== 'YOUR_PROXY_SECRET_KEY') {
    return res.status(401).send({ error: 'Unauthorized Centrifugo proxy request' });
  }

  // 2. Authorize subscription based on `user` (from JWT) and `channel`
  console.log(`Centrifugo wants to authorize subscription for user ${user} to channel ${channel}`);

  let allow = false;
  let info = {}; // Optional info to pass to client

  if (channel.startsWith('user:') && channel === `user:${user}`) {
    // User can subscribe to their own private channel
    allow = true;
    info = { type: 'user_notifications' };
  } else if (channel.startsWith('chat:')) {
    // Example: Check if user is a member of the chat room
    const roomId = channel.split(':')[1];
    // In a real app, you'd query a DB here: `isUserInRoom(user, roomId)`
    if (user === 'user123' && roomId === 'general') { // Placeholder logic
      allow = true;
      info = { chatName: 'General Chat' };
    }
  }

  if (allow) {
    res.json({ result: { channel, status: { authorized: true, info: info } } });
  } else {
    res.json({ result: { channel, status: { authorized: false } } });
  }
});

app.listen(port, () => {
  console.log(`Backend proxy listening at http://localhost:${port}`);
});

Frontend (JavaScript client subscribing to private channel):

import { Centrifuge } from 'centrifuge';

// Assume you have a token for 'user123'
const serverGeneratedToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // For user123

const centrifuge = new Centrifuge("ws://localhost:8000/connection/websocket", {
  token: serverGeneratedToken
});

centrifuge.on('connected', function (ctx) {
  console.log('Connected as client:', ctx.client);

  // Subscribe to a private channel for this user
  const userChannel = `user:${ctx.client}`; // e.g., user:user123
  const privateSub = centrifuge.subscribe(userChannel);

  privateSub.on('publication', function (ctx) {
    console.log(`Message on ${userChannel}:`, ctx.data);
  });

  privateSub.on('error', function (ctx) {
    if (ctx.code === 109) { // Centrifugo error code for unauthorized subscription
      console.error(`Subscription to ${userChannel} unauthorized:`, ctx.message);
    } else {
      console.error(`Error on ${userChannel}:`, ctx);
    }
  });
});

centrifuge.connect();

Best Practices

  • Always use JWTs for client authentication. Never allow unauthenticated connections to access sensitive channels or publish data.
  • Design channel names thoughtfully. Use namespaces (e.g., public:, private:, user:) and include entity IDs (e.g., chat:room_id, order:order_id) to organize and secure your real-time data flow.
  • Implement server-side authorization for private channels. Use Centrifugo's connect_proxy_url and subscribe_proxy_url to let your backend determine access rights.
  • Secure Centrifugo's HTTP API. Use a strong api_key and ensure the API endpoint is only accessible from trusted backend services, not directly from the internet.
  • Handle connection and subscription lifecycle events. The client SDK provides connected, disconnected, error, join, leave events; use them to manage UI state, implement reconnection logic, and log issues.
  • Leverage Centrifugo's features for advanced use cases. Explore presence (who is online), history (retrieve past messages), and message recovery (resync after brief disconnection) to build richer real-time experiences.
  • Monitor Centrifugo instances. Integrate Centrifugo's Prometheus metrics endpoint into your monitoring stack to track connection counts, message rates, and errors.

Anti-Patterns

Exposing the API key to clients. The api_key grants full control over Centrifugo's API; never embed it in client-side code. Use it only from your trusted backend services.

Not using JWT for authentication. Allowing anonymous connections or relying solely on client-side logic for access control is a security vulnerability. Always issue short-lived, signed JWTs from your backend.

Over-reliance on client-side authorization. Do not assume a client is authorized to subscribe to a channel just because they request it. Implement robust authorization checks on your backend using subscribe_proxy_url.

Ignoring connection lifecycle events. Failing to handle disconnected or error events means your application will not react gracefully to network changes or server issues, leading to a poor user experience.

Broadcasting sensitive data to public channels. Any data published to a public channel can be seen by anyone. Ensure sensitive information is only sent to authenticated users on private, authorized channels.

Install this skill directly: skilldb add realtime-services-skills

Get CLI access →