Centrifugo 6.10.0
Overview
This guide covers launching and using the Centrifugo 6.10.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud streaming and messaging listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.
Centrifugo is an open-source, scalable real-time messaging server. Your users' browsers and apps keep a WebSocket connection open to Centrifugo and subscribe to channels. Your backend publishes into those channels through a simple server HTTP API, and Centrifugo pushes each message to every subscriber at once.
Clients prove who they are with a JSON Web Token (JWT) that your backend signs, so Centrifugo never needs access to your user database. It suits chat, live comments and notifications, multiplayer and collaborative tools, live dashboards and streaming AI responses, with a backend written in any language.
What you get:
- No default login, no shared secret. The JWT signing key, the server API key, the admin password and the admin session secret are generated on your instance at first boot and written to
/etc/centrifugo/config.jsonand to/root/centrifugo-info.txt(root only). The image ships no configuration at all. - HTTPS and WSS from the first boot. nginx serves Centrifugo on port 443 with a certificate generated on your instance, port 80 redirects to HTTPS, and requests for host names the instance does not serve are dropped. Centrifugo itself listens on
127.0.0.1:8000only, and its service refuses to stay up if it ever finds itself on another address. - Cross-site WebSocket hijacking blocked. WebSocket connections are accepted on
/connection/websocketonly. A browser page from an origin you have not allowed can neither open a socket nor send a request. nginx answers 403, and Centrifugo's ownclient.allowed_originscarries the same list. You allow your web app with one command (Step 8). Non-browser clients (backends, mobile apps) send no origin and connect normally. - Admin login protected. The admin web UI needs the per-instance password. nginx rate-limits the login, on top of Centrifugo's own throttling of failed attempts.
- Fail closed. Centrifugo and nginx start only after the boot service has written your secrets and configuration. If it cannot finish, nothing is served.
- Hardened defaults. Centrifugo, nginx and the boot service are blocked from the instance metadata service. Centrifugo can fetch URLs you configure, such as JWKS endpoints and proxy backends, so this block means a misconfiguration can never reach the instance's credentials. The AMI requires IMDSv2. nginx hands Centrifugo the real client address from the connection and the configured host name, never values a client sends. Centrifugo runs under systemd sandboxing as an unprivileged user.
- Current release. Centrifugo 6.10.0 is the upstream release binary, verified against the project's published sha256 checksum. It fixes the security advisories that affect 6.9.1 and earlier.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
The image contains Centrifugo 6.10.0 (the checksum-verified upstream release), nginx 1.24 (from Ubuntu 24.04), cloud-init and the AWS Systems Manager agent.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair, and a VPC subnet with a route to the internet (or a private route that you use for management).
- A security group that allows TCP 22 (SSH) from your admin network and TCP 443 (WebSocket clients, the server API and the admin UI) from the networks your clients and backend use. Also allow TCP 80 if you want plain HTTP requests redirected to HTTPS. Never open 8000, because Centrifugo listens on the loopback address only.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Centrifugo 6.10.0 on Ubuntu 24.04 as the software version and pick your Region, then choose Launch through EC2. Choose m5.large, your key pair and a security group as described above.
To launch with the AWS CLI instead, replace the values in angle brackets:
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=centrifugo}]'First boot takes a few seconds after the instance is running.
Connect to your instance
For Centrifugo 6.10.0 on Ubuntu 24.04, the SSH user is ubuntu.
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
The boot service generates the secrets, the configuration and the certificate. Then Centrifugo and nginx start:
systemctl is-active centrifugo nginx centrifugo-firstboot
sudo test -f /var/lib/sac/data/centrifugo-firstboot.done && echo FIRSTBOOT_DONEAll three units are active and the first boot marker is present:
active active active FIRSTBOOT_DONEOnly SSH and nginx listen on network ports. Centrifugo listens on the loopback address:
sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort0.0.0.0:22 users:(("sshd"),("systemd")) 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx")) 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx")) 127.0.0.1:8000 users:(("centrifugo")) [::]:22 users:(("sshd"),("systemd")) [::]:443 users:(("nginx"),("nginx"),("nginx")) [::]:80 users:(("nginx"),("nginx"),("nginx"))Get your URL and secrets
sudo cat /root/centrifugo-info.txtThe file is readable by root only:
# Centrifugo 6.10.0 on Ubuntu 24.04 - generated on first boot by centrifugo-firstboot.service.
# Every secret below is unique to this instance; none was baked into the image. Keep this file secret.
# Admin web UI: open CENTRIFUGO_URL and log in with CENTRIFUGO_ADMIN_PASSWORD (there is no username).
# Server HTTP API: POST ${CENTRIFUGO_URL}api/<method> with the header "X-API-Key: <CENTRIFUGO_API_KEY>".
# Client connections: wss://<host>/connection/websocket with a JWT signed (HS256) by CENTRIFUGO_TOKEN_HMAC_SECRET_KEY.
# Browser apps on another origin:
sudo centrifugo-allow-origin add https://your-app.example
# The certificate is self signed (minted on this instance); install your own in /etc/nginx/ssl. CENTRIFUGO_URL=https://<public-ip>/ CENTRIFUGO_ADMIN_PASSWORD=<CENTRIFUGO_ADMIN_PASSWORD> CENTRIFUGO_API_KEY=<CENTRIFUGO_API_KEY> CENTRIFUGO_TOKEN_HMAC_SECRET_KEY=<CENTRIFUGO_TOKEN_HMAC_SECRET_KEY> CENTRIFUGO_ADMIN_SECRET=<CENTRIFUGO_ADMIN_SECRET>
# Re-verify the appliance:
sudo centrifugo-selfcheck Change the served name:
sudo centrifugo-set-hostname <dns-name>
# User guide: https://secureanycloud.com/stacks/centrifugo-6-10-0/Each value has one user:
- CENTRIFUGO_ADMIN_PASSWORD: you use it to log in to the admin web UI.
- CENTRIFUGO_API_KEY: your backend uses it to publish through the server HTTP API.
- CENTRIFUGO_TOKEN_HMAC_SECRET_KEY: your backend uses it to sign the connection JWTs it hands to clients. Never give it to a client.
- CENTRIFUGO_ADMIN_SECRET: Centrifugo itself uses it to sign admin UI sessions.
The URL uses the instance's public IP address from the EC2 instance metadata (the private address when the instance has no public one). The certificate also names the public DNS name and the private IP address.
If you stop and start the instance without an Elastic IP, the boot service picks up the new address and issues a new certificate for it. To serve Centrifugo under your own DNS name, see Step 9.
The admin web UI
Open https://<public-ip>/ in your browser, accept the self-signed certificate and log in with CENTRIFUGO_ADMIN_PASSWORD (there is no user name).
The Status page lists the running node with its version, uptime, connected clients and subscriptions. The screenshot at the top of this guide shows one client subscribed to demo:chat. Actions runs any server API command from the browser, for example a publish into a channel:
Channels lists the channels that currently have subscribers:
The same login works from the command line. On the instance, the commands below use https://127.0.0.1/, because all of the instance's own names are served. From elsewhere, use your CENTRIFUGO_URL. The password is passed on standard input, never on the command line:
CF_URL=https://127.0.0.1/
sudo sed -n 's/^CENTRIFUGO_ADMIN_PASSWORD=//p' /root/centrifugo-info.txt | tr -d '\n' \
| curl -sk -m 10 --data-urlencode password@- "${CF_URL}admin/auth" | jq -r 'keys[]'tokenPublish with the server API
Your backend publishes with POST <CENTRIFUGO_URL>api/<method> and the header X-API-Key. Without the key the API answers 401:
CF_URL=https://127.0.0.1/ curl -sk -m 10 -o /dev/null -w '%{http_code}\n' --data '{"channel":"demo:news","data":{"text":"no key"}}' "${CF_URL}api/publish"401With the key, publish into a channel, read the channel history back and look at the node. The image ships a demo namespace that keeps the last 100 messages of each channel for five minutes, so demo:news has history. The key reaches curl in a configuration read from standard input, so it never appears in the process list:
CF_URL=https://127.0.0.1/
CF_KEY=$(sudo sed -n 's/^CENTRIFUGO_API_KEY=//p' /root/centrifugo-info.txt) printf 'header = "X-API-Key: %s"\n' "${CF_KEY}" | curl -sk -m 10 -K - --data '{"channel":"demo:news","data":{"text":"Hello from the server API"}}' "${CF_URL}api/publish"; echo printf 'header = "X-API-Key: %s"\n' "${CF_KEY}" | curl -sk -m 10 -K - --data '{"channel":"demo:news","limit":5}' "${CF_URL}api/history" | jq -c '.result.publications[] | .data' printf 'header = "X-API-Key: %s"\n' "${CF_KEY}" | curl -sk -m 10 -K - --data '{}' "${CF_URL}api/info" | jq -c '.result.nodes[] | {name, version, uptime}'{"result":{"offset":1,"epoch":"ikLMATso"}} {"text":"Hello from the server API"} {"name":"ip-172-31-88-136_8000","version":"6.10.0 OSS","uptime":555}The full list of server API methods (publish, broadcast, subscribe, unsubscribe, disconnect, presence, history, channels, info and more) is in the Centrifugo server API documentation.
Connect clients over WebSocket
Clients connect to wss://<public-ip>/connection/websocket (or your DNS name) with a connection JWT signed with HS256 using CENTRIFUGO_TOKEN_HMAC_SECRET_KEY. Your backend creates the token after it has authenticated the user. sub is the user ID and exp is the expiry. The optional channels claim subscribes the connection to channels on the server side.
As an illustration, this generates a one-hour token for user user-42, subscribed to demo:news, on the instance itself:
sudo python3 -I - <<'EOF' import base64, hashlib, hmac, json, time key = next(l.split("=", 1)[1].strip() for l in open("/root/centrifugo-info.txt") if l.startswith("CENTRIFUGO_TOKEN_HMAC_SECRET_KEY=")) b64 = lambda b: base64.urlsafe_b64encode(b).rstrip(b"=").decode() head = b64(json.dumps({"alg": "HS256", "typ": "JWT"}).encode()) body = b64(json.dumps({"sub": "user-42", "exp": int(time.time()) + 3600, "channels": ["demo:news"]}).encode()) sig = b64(hmac.new(key.encode(), f"{head}.{body}".encode(), hashlib.sha256).digest()) print(f"{head}.{body}.{sig}") EOFeyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAidXNlci00MiIsICJleHAiOiAxNzkyNDc3MjE0LCAiY2hhbm5lbHMiOiBbImRlbW86bmV3cyJdfQ.r8NL_7Ck_kdDfSuZZM9TN-4Nhw3JDKNE6cT3QXLwt5kIn a real application, use a JWT library in your backend language. In the browser, the official centrifuge-js SDK connects with that token and receives every message published into the channel:
import { Centrifuge } from 'centrifuge'; const client = new Centrifuge('wss://<public-ip>/connection/websocket', { token: '<connection JWT from your backend>' }); // Publications into server-side subscribed channels (the token's "channels" claim). client.on('publication', (ctx) => console.log(ctx.channel, ctx.data)); client.connect();Official SDKs exist for JavaScript, Dart/Flutter, Swift, Java, Python, Go and .NET. Subscribing to channels from the client side needs either a subscription token or channel namespace permissions. See channel permissions.
While the instance uses its self-signed certificate, browsers only connect after you have accepted the certificate once at https://<public-ip>/. For production, install a trusted certificate (Step 9).
Allow your web app's origin
Browsers send an Origin header with WebSocket connections. The instance accepts only its own https origins until you add yours, so a page on another site cannot connect on behalf of your users. A connection from a foreign origin is refused before it reaches Centrifugo:
CF_URL=https://127.0.0.1/ curl -sk -m 10 --http1.1 -o /dev/null -w '%{http_code}\n' -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
-H 'Origin: https://attacker.example' "${CF_URL}connection/websocket"403Add the origin of each web app that connects (scheme, host and optional port, no path). The command updates both the nginx guard and Centrifugo's client.allowed_origins, then restarts Centrifugo. Open connections reconnect automatically:
sudo centrifugo-allow-origin add https://app.<your-domain>sudo centrifugo-allow-origin list shows the origins you have added and sudo centrifugo-allow-origin remove <origin> takes one away. The list is kept in /etc/sac/centrifugo-allowed-origins.
Your own DNS name and certificate
Point a DNS record at the instance, then tell Centrifugo to serve that name. The command updates nginx, issues a new self-signed certificate that names it, adds https://<name> to the allowed origins and updates the URL in the credentials file:
sudo centrifugo-set-hostname centrifugo.<your-domain>The name is saved in /etc/sac/centrifugo-hostname and survives reboots.
To use a certificate from your own CA or from Let's Encrypt, replace /etc/nginx/ssl/centrifugo.crt and /etc/nginx/ssl/centrifugo.key (keep the key mode 0600), then run sudo systemctl reload nginx.
Configuration and rotating secrets
Centrifugo reads /etc/centrifugo/config.json (owned by root, readable by the centrifugo group). Everything except the secrets:
sudo jq 'del(.client.token, .http_api, .admin.password, .admin.secret)' /etc/centrifugo/config.json{ "http_server": { "address": "127.0.0.1", "port": 8000 }, "log": { "level": "info" }, "client": { "allowed_origins": [ "https://<public-ip>", "https://ec2-<public-ip-dashed>.compute-1.amazonaws.com", "https://<private-ip>", "https://ip-<private-ip-dashed>.ec2.internal" ] }, "admin": { "enabled": true }, "channel": { "namespaces": [ { "name": "demo", "history_size": 100, "history_ttl": "300s" } ] } }Add your own channel namespaces, proxy settings or engine (for example Redis for several nodes) as described in the configuration documentation. Keep http_server.address at 127.0.0.1. Check the file with sudo centrifugo checkconfig --config /etc/centrifugo/config.json and restart with sudo systemctl restart centrifugo. Manage allowed_origins with centrifugo-allow-origin rather than by hand.
To rotate a secret, write a new value with jq, restart Centrifugo, then update the credentials file. For example, to set a new admin password:
NEW=<new-password>
sudo jq --arg p "$NEW" '.admin.password = $p' /etc/centrifugo/config.json | sudo tee /etc/centrifugo/config.new.json >/dev/null
sudo chown root:centrifugo /etc/centrifugo/config.new.json && sudo chmod 0640 /etc/centrifugo/config.new.json
sudo mv /etc/centrifugo/config.new.json /etc/centrifugo/config.json && sudo systemctl restart centrifugo
sudo sed -i "s/^CENTRIFUGO_ADMIN_PASSWORD=.*/CENTRIFUGO_ADMIN_PASSWORD=$NEW/" /root/centrifugo-info.txtThe same method works for .http_api.key (the API key) and .client.token.hmac_secret_key (the JWT signing key). Tokens signed with the old key stop working.
Security checks
centrifugo-selfcheck re-verifies the whole appliance with this instance's secrets. It checks HTTPS, the admin login, the API key, a publish and history round trip, and a WebSocket connection with a JWT that receives a message published through the API. It also checks that foreign origins, foreign-key tokens and other host names are refused, that the port is loopback-only, and that the metadata block is in place inside every service:
sudo centrifugo-selfcheckCentrifugo appliance self-check (https://<public-ip>/) [ok] HTTPS admin web UI -> 200 [ok] admin login with the per-instance password -> token [ok] wrong admin password refused (400) [ok] server API without a key / with a wrong key -> 401 [ok] server API with the per-instance key -> 200, node version 6.10.0 OSS [ok] publish -> history round trip on demo:history (sac-hist-1792473018281393963) [ok] cross-origin WebSocket upgrade -> 403 (nginx Origin guard) [ok] WebSocket upgrade outside /connection/websocket -> 403 [ok] JWT (per-instance HMAC key) connects over wss; an API publication reached the subscriber (sac-ws-1792473018-pkBoMKMP) [ok] same-origin page (https://<public-ip>) connects [ok] JWT signed with a foreign key refused ({"close": 3500}) [ok] Centrifugo's own client.allowed_origins refuses a foreign Origin (403 on 127.0.0.1:8000) [ok] port 80 -> 301 https://<public-ip>/; unknown Host -> 444; CONNECT refused; cross-origin POST -> 403 [ok] Centrifugo (8000) answers on 127.0.0.1 only [ok] instance metadata refused inside the centrifugo and nginx cgroups (loopback control 200) SELFCHECK OK - HTTPS, per-instance secrets, WebSocket round trip, origin guard and metadata block verified.The metadata block is a systemd IPAddressDeny rule on each service. You can see it in effect:
for u in centrifugo nginx centrifugo-firstboot; do printf '%s: ' $u; systemctl show -p IPAddressDeny --value $u.service; donecentrifugo: 169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32 nginx: 169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32 centrifugo-firstboot: 169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32Versions, provenance and file locations
centrifugo version sed -n '1,6p' /usr/share/doc/centrifugo/BUILD-PROVENANCE.txtCentrifugo v6.10.0 (Go version: go1.27.2) Centrifugo 6.10.0 - official upstream release binary (not rebuilt) source https://github.com/centrifugal/centrifugo/releases/download/v6.10.0/centrifugo_6.10.0_linux_amd64.tar.gz sha256 6453d58c17f54bf2e1fed0d4e3db0b79779979b304387e89e6753408a8c6e8ba (upstream centrifugo_6.10.0_checksums.txt) licence Apache License 2.0 (LICENSE from the release tarball) version Centrifugo v6.10.0 (Go version: go1.27.2)These files and logs matter:
- /etc/centrifugo/config.json: the Centrifugo configuration and secrets (written at first boot).
- /root/centrifugo-info.txt: the URL and secrets (root only).
- /etc/nginx/sites-available/sac-centrifugo: the HTTPS front door. The certificate and key are in /etc/nginx/ssl/.
- /etc/sac/: your served host name and added origins (when set).
- /usr/share/doc/centrifugo/: LICENSE, README, CHANGELOG, BUILD-PROVENANCE.txt.
- journalctl -u centrifugo: Centrifugo's log (also nginx, centrifugo-firstboot).
Backup and maintenance
- The built-in memory engine keeps channel history and presence in RAM only, so a restart clears them. There is nothing to back up except
/etc/centrifugo,/etc/sacand/etc/nginx/ssl. For persistence or several nodes, configure a Redis engine. - Ubuntu security updates install automatically (unattended-upgrades). Centrifugo is a single binary in
/usr/local/bin. To upgrade, launch the newer version from the listing and move your configuration across. - Centrifugo is light: an m5.large holds tens of thousands of idle connections. Raise the instance size for heavy publish rates.
Security notes
- Limit TCP 443 in the security group to the networks your clients and backend use, and TCP 22 to your admin network. If only your backend needs the admin UI and server API, consider a separate private load balancer or VPN for them.
- Serve Centrifugo under your own DNS name with a certificate from a trusted CA (Step 9) so clients can verify it.
- Keep
CENTRIFUGO_TOKEN_HMAC_SECRET_KEYandCENTRIFUGO_API_KEYon your backend only, and issue short-lived connection tokens. - Allow only the origins of your own web apps (Step 8). Never add
*. - Keep Centrifugo on
127.0.0.1, and expose nothing but nginx.
Support and licensing
SecureAnyCloud provides 24/7 technical support for this image by email and live chat: sales@secureanycloud.com. Centrifugo is licensed under the Apache License 2.0, and its LICENSE file is on the instance in /usr/share/doc/centrifugo. Centrifugo is a trademark of its respective owner. SecureAnyCloud is not affiliated with or endorsed by the Centrifugo project.
Deploy on AWS
Launch Centrifugo 6.10.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com