Centrifugo real-time messaging relay for WebSocket pub/sub.
Overview¶
Centrifugo is a production-grade Go-based real-time messaging server running at relay.spacemusic.tv. It provides WebSocket pub/sub with channels, presence tracking, and message history.
Version: v6.6.2 (centrifugo/centrifugo:v6)
Why Centrifugo: The centrifuge-csharp SDK works identically in Avalonia desktop and Avalonia WASM (browser), enabling one C# codebase for all client platforms. See the relay service's research doc for the full evaluation of alternatives (Node-RED, custom Node.js, custom Go, MQTT).
Endpoints¶
| Endpoint | URL |
|---|---|
| WebSocket | wss://relay.spacemusic.tv/connection/websocket |
| HTTP Publish API | https://relay.spacemusic.tv/api/publish |
| HTTP Broadcast API | https://relay.spacemusic.tv/api/broadcast |
| HTTP Batch API | https://relay.spacemusic.tv/api/batch |
| Admin UI | https://relay.spacemusic.tv/ (password-protected) |
| Prometheus Metrics | https://relay.spacemusic.tv/metrics |
The Admin UI is served on the same port as all other endpoints (admin.external: true in config), since Traefik only routes to one port per service.
Channel Namespaces¶
Centrifugo organizes channels into namespaces with different capabilities:
| Namespace | Auth | Presence | History | Client Publish | Anonymous |
|---|---|---|---|---|---|
session:{id} |
JWT required | Yes (join/leave events) | 100 msgs / 300s TTL | Subscribers can publish | No |
public:{id} |
None | Yes | No | Anyone can publish | Yes |
system:{topic} |
JWT required | Yes | 50 msgs / 600s TTL | No (server-push only) | No |
session:{id} -- For live performance sessions. Authenticated clients join with a JWT, can see who's present, and publish messages. History is preserved for late joiners (force_recovery enabled).
public:{id} -- For public status feeds. No authentication required, anonymous connections allowed. Good for public-facing status displays.
system:{topic} -- For system announcements and alerts. Only the server can publish (via HTTP API). Clients need a JWT to subscribe. History is preserved for 10 minutes.
Authentication¶
Centrifugo uses JWT tokens with HMAC HS256 for client authentication:
- Connection token -- Required to establish a WebSocket connection (for
session:andsystem:namespaces). Containssub(user ID) claim. - Subscription token -- Required for private channel subscriptions. Must include both
subandchannelclaims.
Tokens can be generated via the API gateway at api.spacemusic.tv/api/relay/tokens.
The HTTP API uses an API key via the Authorization header for server-to-server communication.
Allowed Origins¶
WebSocket connections are restricted to:
relay.spacemusic.tvstream.spacemusic.tvconnect.spacemusic.tvdashboard.spacemusic.tv
HTTP API¶
The server-side HTTP API enables publishing from any backend service:
# Publish a message to a channel
curl -X POST https://relay.spacemusic.tv/api/publish \
-H "Authorization: apikey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"channel": "session:room1", "data": {"type": "note", "value": 60}}'
# Broadcast to multiple channels
curl -X POST https://relay.spacemusic.tv/api/broadcast \
-H "Authorization: apikey YOUR_API_KEY" \
-d '{"channels": ["public:status", "public:display"], "data": {"status": "live"}}'
All HTTP API endpoints are also available through the API gateway at api.spacemusic.tv/api/relay/*.
Deployment¶
Centrifugo runs via Docker Compose on the devpush_default network. Deployed via GitHub Actions SSH to /opt/spacemusic/relay/spacemusic-relay/.
Environment variables use the CENTRIFUGO_ prefix with underscored JSON path:
| Variable | Purpose |
|---|---|
CENTRIFUGO_CLIENT_TOKEN_HMAC_SECRET_KEY |
JWT verification secret |
CENTRIFUGO_API_KEY |
Server-side HTTP API key |
CENTRIFUGO_ADMIN_PASSWORD |
Admin UI login |
CENTRIFUGO_ADMIN_SECRET |
Admin UI session secret |
Gotchas
- Environment variable naming follows the JSON config path with underscores:
CENTRIFUGO_CLIENT_TOKEN_HMAC_SECRET_KEYmaps toclient.token.hmac_secret_keyin config.json - Do not use
env_filein docker-compose -- it injects raw variables into the container causing "unknown var" warnings. Docker Compose reads.envfor substitutions automatically. admin.external: trueis required so the admin UI serves on the main port (Traefik only routes to one port)
Monitoring¶
Prometheus scrapes Centrifugo at spacemusic-centrifugo:8000/metrics. These metrics power the "Relay" Grafana dashboard showing WebSocket connections, messages per second, and channel activity.
Performance¶
Tested benchmarks (2026-03-03):
- Server-side batch of 1,000 messages: 4ms (~250k msg/s equivalent)
- 15 concurrent WebSocket clients, 5 minutes: all operations sub-millisecond (p99 < 300us)
- Memory at idle: 36 MB