Setup


https://github.com/centrifugal/centrifugo
https://centrifugal.dev/docs/getting-started/quickstart
https://centrifugal.dev/docs/server/configuration
https://centrifugal.dev/docs/server/engines
https://centrifugal.dev/docs/server/observability
https://centrifugal.dev/docs/server/tls

1. Linux VM With systemd#

install#

Use the official release artifact or package method that matches your OS. In production, pin an exact version and record it in deployment inventory.

centrifugo version

directories#

sudo install -d -o centrifugo -g centrifugo /etc/centrifugo
sudo install -d -o centrifugo -g centrifugo /var/lib/centrifugo
sudo install -d -o centrifugo -g centrifugo /var/log/centrifugo

config#

{
  "client": {
    "token": {
      "hmac_secret_key": "change-me-from-secret-manager"
    },
    "allowed_origins": ["https://app.example.com"]
  },
  "http_api": {
    "key": "change-me-from-secret-manager"
  },
  "prometheus": {
    "enabled": true
  },
  "channel": {
    "namespaces": [
      {
        "name": "notification",
        "allow_subscribe_for_client": false,
        "presence": false,
        "history_size": 10,
        "history_ttl": "300s"
      }
    ]
  }
}
config notes:
    use environment/secret injection for real secrets
    use exact allowed origins, not wildcard, for browser apps
    keep server API reachable only by backend services
    configure namespaces by access pattern

systemd#

[Unit]
Description=Centrifugo real-time messaging server
After=network-online.target
Wants=network-online.target

[Service]
User=centrifugo
Group=centrifugo
ExecStart=/usr/local/bin/centrifugo --config=/etc/centrifugo/config.json
Restart=always
RestartSec=5
LimitNOFILE=262144
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now centrifugo
sudo systemctl status centrifugo
journalctl -u centrifugo -f

2. Docker#

local run#

mkdir -p centrifugo-local
cd centrifugo-local
cat > config.json <<'EOF'
{
  "client": {
    "token": {
      "hmac_secret_key": "dev-token-secret-change-me"
    },
    "allowed_origins": ["http://localhost:5173"]
  },
  "http_api": {
    "key": "dev-api-key-change-me"
  },
  "prometheus": {
    "enabled": true
  },
  "channel": {
    "namespaces": [
      {
        "name": "chat",
        "allow_subscribe_for_client": true,
        "presence": true,
        "history_size": 20,
        "history_ttl": "300s"
      }
    ]
  }
}
EOF
docker run --rm \
  -d \
  --name centrifugo \
  --ulimit nofile=262144:262144 \
  -p 8000:8000 \
  -v "$PWD/config.json:/centrifugo/config.json:ro" \
  centrifugo/centrifugo:v6 \
  centrifugo --config=/centrifugo/config.json

Verify:

docker logs --tail 50 centrifugo
curl -i http://localhost:8000/health
curl -i http://localhost:8000/metrics

Follow logs when debugging:

docker logs -f centrifugo

Stop:

docker stop centrifugo

docker compose#

services:
  centrifugo:
    image: centrifugo/centrifugo:v6
    command: ["centrifugo", "--config=/centrifugo/config.json"]
    ports:
      - "8000:8000"
    volumes:
      - ./config.json:/centrifugo/config.json:ro
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
    restart: unless-stopped
docker compose up -d
docker compose ps
docker compose logs -f centrifugo

3. K8S With Helm / Manifest#

deployment shape#

recommended production shape:
    Deployment with 2+ replicas
    ConfigMap for non-secret config
    Secret for client.token.hmac_secret_key and http_api.key
    Service ClusterIP
    Ingress / Gateway with HTTPS
    PodDisruptionBudget
    readiness/liveness probes
    Prometheus scrape annotation or ServiceMonitor

minimal manifest sketch#

apiVersion: apps/v1
kind: Deployment
metadata:
  name: centrifugo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: centrifugo
  template:
    metadata:
      labels:
        app: centrifugo
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8000"
        prometheus.io/path: "/metrics"
    spec:
      containers:
        - name: centrifugo
          image: centrifugo/centrifugo:v6
          args: ["centrifugo", "--config=/centrifugo/config.json"]
          ports:
            - containerPort: 8000
          volumeMounts:
            - name: config
              mountPath: /centrifugo/config.json
              subPath: config.json
              readOnly: true
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              memory: 512Mi
      volumes:
        - name: config
          configMap:
            name: centrifugo-config
important:
    do not expose /api to public Internet
    if multiple replicas need shared pub/sub or recovery, configure supported engine
    test reconnect behavior during rolling update

4. High Availability#

single node:
    enough for local dev / small internal tools
    node restart disconnects clients
    no cross-node fan-out problem because only one node exists

multiple nodes:
    put nodes behind load balancer / ingress
    use sticky session only if your deployment requires it
    configure supported engine for cross-node publication and shared state
    watch engine latency and availability
HA checklist:
    at least 2 replicas
    rolling update maxUnavailable controlled
    health probe works
    file descriptor limit sized for expected connections
    backend retry for publish API
    client reconnect and token refresh tested

5. Operations#

publish test#

curl -s http://localhost:8000/api/publish \
  -H "Content-Type: application/json" \
  -H "X-API-Key: dev-api-key-change-me" \
  -d '{
    "channel": "chat:index",
    "data": {"text": "hello from backend"}
  }'

logs#

docker compose logs -f centrifugo
journalctl -u centrifugo -f
kubectl logs -f deploy/centrifugo

incident checklist#

clients cannot connect:
    check browser console
    check client.allowed_origins
    check token signature and exp
    check ingress websocket upgrade / timeout
    check Centrifugo logs

publish succeeds but clients receive nothing:
    verify channel name exactly matches
    verify client subscribed successfully
    verify namespace allows client-side subscription if used
    check API key and publish response
    check multi-node engine config

reconnect storm:
    check recent deploy / ingress restart
    check token refresh endpoint
    check node CPU/memory/file descriptors
    check Redis / engine latency