Links#
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/tls1. 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 versiondirectories#
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/centrifugoconfig#
{
"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 patternsystemd#
[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.targetsudo systemctl daemon-reload
sudo systemctl enable --now centrifugo
sudo systemctl status centrifugo
journalctl -u centrifugo -f2. Docker#
local run#
mkdir -p centrifugo-local
cd centrifugo-localcat > 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"
}
]
}
}
EOFdocker 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.jsonVerify:
docker logs --tail 50 centrifugo
curl -i http://localhost:8000/health
curl -i http://localhost:8000/metricsFollow logs when debugging:
docker logs -f centrifugoStop:
docker stop centrifugodocker 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-stoppeddocker compose up -d
docker compose ps
docker compose logs -f centrifugo3. 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 ServiceMonitorminimal 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-configimportant:
do not expose /api to public Internet
if multiple replicas need shared pub/sub or recovery, configure supported engine
test reconnect behavior during rolling update4. 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 availabilityHA 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 tested5. 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/centrifugoincident 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