Sockudo 4.7.0 on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and use of Sockudo 4.7.0 on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images.
Sockudo is a realtime WebSocket server written in Rust that implements the Pusher protocol. Browser clients open a WebSocket, subscribe to channels and receive events as they happen; your backend publishes those events over a signed HTTP API. Because the protocol matches, the client and server libraries your application already uses talk to this server unchanged, and the realtime layer moves onto infrastructure you control.
This image ships the server. It is the piece you deploy once and point every application at.
What is included
| Component | Version | Role |
|---|---|---|
| Sockudo | 4.7.0 | The realtime server: WebSocket transport plus the Pusher HTTP API |
| nginx | Ubuntu 24.04 | TLS termination on 443 and the WebSocket upgrade proxy |
| Ubuntu Server | 24.04 LTS | Fully patched base operating system |
Key facts:
| Item | Value |
|---|---|
| Default SSH user | azureuser |
| Public port | 443 (HTTPS and WSS). Port 80 redirects to 443 |
| Server listener | 127.0.0.1:6001, reachable only through nginx |
| Metrics listener | 127.0.0.1:9601, loopback only |
| Configuration | /etc/sockudo/config.toml |
| Per VM credentials | /etc/sockudo/sockudo.env and /root/sockudo-credentials.txt |
| Recommended VM size | Standard_B2s (2 vCPU, 4 GiB) |
One VM is the whole product
Sockudo can be scaled horizontally with Redis, NATS, Kafka and others behind it. This image is deliberately configured as a complete standalone node: the local adapter, the memory app manager, the memory cache, the memory queue and the memory rate limiter. There is no database to run, no broker to operate and no cache server to keep alive. The server measured 68 MB of resident memory while serving, against the 4 GiB of a Standard_B2s.
One configuration detail is worth knowing if you ever hand edit the file: Sockudo's compiled default for the queue driver is redis, so a configuration that simply omits the queue section makes the server try to reach a Redis that does not exist. This image states driver = "memory" explicitly for exactly that reason.
Security by design, and what it replaces
Upstream ships a working application whose credentials are published. The config.toml and config.json in the Sockudo repository both define an enabled application with id app-id, key app-key and secret app-secret, and .env.example publishes demo-app / demo-key / demo-secret. Those strings appear in the project's own getting started documentation. On a machine with a public address that means anybody who has read those docs can publish to, and subscribe from, every channel on your bus.
This image does not rotate that credential. It never creates it.
-
The shipped configuration contains no application at all.
/etc/sockudo/config.tomlhas no application definition, so out of the box the server cannot authenticate anybody. -
Your VM generates its own application on first boot.
sockudo-firstboot.servicecreates an application id, a 32 character key and a 48 character secret fromopensslon your machine, and writes them to/etc/sockudo/sockudo.env(mode0640, readable only by root and the service account). Nothing is baked into the image, so no two customers ever share an application. -
The server physically cannot start before that has happened.
sockudo.servicecarriesConditionPathExistson a bootstrap marker that first boot writes only after the credentials exist. This is a condition rather than an ordering rule on purpose: anAfter=edge still starts the server if first boot fails or runs slowly, which is precisely the window in which a default credential would be live. -
The server refuses to start on a published credential. A guard runs before the server opens its socket and aborts if any of the six published values is in effect, or if one of them has been pasted into the configuration file.
-
First boot proves itself. It does not report success until its own freshly generated credential has completed a real publish and subscribe round trip on your machine.
-
There is no unencrypted path off the VM. Sockudo binds to loopback; nginx terminates TLS on 443 with a certificate generated for your VM at first boot.
-
The metrics endpoint is closed off. Upstream defaults its Prometheus listener to
0.0.0.0:9601with no authentication. Here it is bound to loopback, and the unauthenticated/usageand/statsintrospection endpoints are disabled.
The TLS certificate is self signed. It is generated on your VM for your VM's address, which means the connection is encrypted but a browser or client will not trust it until you either install your own certificate or explicitly trust this one. Replacing it is two files and a reload, and is covered below. Do not disable certificate verification in your application as a way around it.
A note on the licence
Sockudo is MIT licensed. The LICENSE file at tag v4.7.0 opens with the line MIT License and carries the standard MIT body. The project's Cargo.toml points at that file rather than declaring an SPDX identifier, which is why some automated scanners report the licence as unknown; the file itself is unambiguous.
Prerequisites
- An active Azure subscription.
- Permission to create virtual machines and network security rules.
- An SSH key pair for
azureuser. - A network security rule allowing inbound TCP 443 from the clients that need realtime, and TCP 22 from your management network only.
- Recommended VM size:
Standard_B2s(2 vCPU, 4 GiB). Sockudo is a lean Rust binary; this size has substantial headroom for tens of thousands of connections.
Step 1: Deploy the Virtual Machine
Option A: Azure Portal
- In the Azure Portal choose Create a resource and search the Marketplace for Sockudo on Ubuntu 24.04 LTS by cloudimg.
- Select the offer and choose Create.
- Under Basics, pick your subscription, resource group and region, and set the VM size to Standard_B2s.
- Set the authentication type to SSH public key and the username to azureuser.
- Under Inbound port rules, allow HTTPS (443) and SSH (22).
- Choose Review + create, then Create.
Option B: Azure CLI
az vm create \
--resource-group my-realtime-rg \
--name sockudo-01 \
--image cloudimg:sockudo:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
# Then open the port your clients need
az vm open-port --resource-group my-realtime-rg --name sockudo-01 --port 443 --priority 1001
Step 2: Connect via SSH
# Find the address, then connect
az vm show -d -g my-realtime-rg -n sockudo-01 --query publicIps -o tsv
ssh azureuser@<vm-ip>
Step 3: Confirm first boot completed
The first boot service generates your application credentials and TLS certificate, starts the server, and proves the result before it reports success. On a Standard_B2s it finishes within a few seconds of the machine becoming reachable.
sudo systemctl is-active sockudo-firstboot.service sockudo.service nginx.service
Expected output:
active
active
active

If sockudo.service reports inactive, first boot has not finished. Its log records every step:
sudo journalctl -u sockudo-firstboot.service --no-pager | tail -20
Step 4: Read your application credentials
Your application id, key and secret exist only on this machine.
sudo cat /root/sockudo-credentials.txt
The file records the following, with the values generated for your VM:
SOCKUDO_APP_ID=cloudimg-...
SOCKUDO_APP_KEY=...
SOCKUDO_APP_SECRET=...
SOCKUDO_URL=https://<server-ip>/
SOCKUDO_WS_URL=wss://<server-ip>/app/<app-key>

The key is public and the secret is not. The app key is what browser clients connect with, and it is meant to be visible in your frontend. The app secret signs server side HTTP API calls and must never reach a browser.
To see the non secret values on their own:
sudo grep -E '^(SOCKUDO_APP_ID|SOCKUDO_URL|SOCKUDO_WS_URL)=' /root/sockudo-credentials.txt
Step 5: Check the server is healthy
Sockudo exposes two unauthenticated health endpoints through nginx. -k is used here only because the shipped certificate is self signed and this call is to the machine itself.
curl -sk -o /dev/null -w 'live: %{http_code}\n' https://127.0.0.1/live
curl -sk https://127.0.0.1/up; echo
Expected output:
live: 200
OK
/up is the readiness endpoint and is the one to point a load balancer at. It reports OK when the server is healthy, DEGRADED when a non critical subsystem is unhappy, and returns 503 with ERROR if the server has no application configured or a core subsystem has failed.
There is a per application form as well:
APP_ID=$(sudo sed -n 's/^SOCKUDO_APP_ID=//p' /root/sockudo-credentials.txt)
curl -sk -o /dev/null -w 'per-app readiness: %{http_code}\n' "https://127.0.0.1/up/${APP_ID}"
Expected output:
per-app readiness: 200
Step 6: Publish an event
Publishing is an HTTP POST signed with your app secret. The signature is an HMAC SHA256, in hex, over the request method, the request path and the sorted query string, joined by newlines. When the body is not empty, an MD5 of the body must be included in the query and therefore in the signature.
This snippet reads your credentials from the file and publishes one event:
APP_ID=$(sudo sed -n 's/^SOCKUDO_APP_ID=//p' /root/sockudo-credentials.txt)
KEY=$(sudo sed -n 's/^SOCKUDO_APP_KEY=//p' /root/sockudo-credentials.txt)
SECRET=$(sudo sed -n 's/^SOCKUDO_APP_SECRET=//p' /root/sockudo-credentials.txt)
BODY='{"name":"order-created","channel":"orders","data":"{\"id\":42}"}'
TS=$(date +%s)
MD5=$(printf '%s' "$BODY" | md5sum | cut -d' ' -f1)
QS="auth_key=${KEY}&auth_timestamp=${TS}&auth_version=1.0&body_md5=${MD5}"
SIG=$(printf 'POST\n/apps/%s/events\n%s' "$APP_ID" "$QS" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/.*= //')
curl -sk -X POST "https://127.0.0.1/apps/${APP_ID}/events?${QS}&auth_signature=${SIG}" \
-H 'Content-Type: application/json' -d "$BODY"; echo
Expected output:
{"ok":true}

A wrong signature is refused with 401, which you can confirm by changing one character of the secret. In practice you will not hand roll this: any Pusher server SDK produces the same signature. In PHP, Node, Python, Go or Ruby, point the SDK's host at your VM, set the scheme to HTTPS and the port to 443, and pass your app id, key and secret.
Step 7: Subscribe from a browser
Any Pusher client library connects. With pusher-js:
import Pusher from 'pusher-js';
const pusher = new Pusher('<app-key>', {
wsHost: 'your-vm-address',
wssPort: 443,
forceTLS: true,
enabledTransports: ['ws', 'wss'],
cluster: '',
});
const channel = pusher.subscribe('orders');
channel.bind('order-created', (data) => {
console.log('order', data.id);
});
The WebSocket URL underneath is wss://your-vm-address/app/<app-key>. The server answers a new connection with a pusher:connection_established frame carrying a socket_id and the activity timeout, then pusher_internal:subscription_succeeded once you subscribe.
Until you install a trusted certificate, a browser will refuse the WebSocket because the certificate is self signed. Visit https://your-vm-address/live once and accept the certificate, or install your own certificate as described below.
Step 8: Prove the whole path end to end
The image ships the same check the build gate uses. It opens a real WebSocket subscriber, publishes over the signed HTTP API on a separate connection, and asserts the exact payload arrives. It then confirms that forged signatures and the credentials published upstream are refused, and re confirms that yours still works.
sudo /usr/local/sbin/sockudo-roundtrip-check.sh
Expected output ends with:
OK sockudo round trip complete - every assertion passed

This is a safe command to run at any time; it creates and tears down its own connections and does not modify configuration.
Step 9: Metrics
Prometheus metrics are served on 127.0.0.1:9601 and are deliberately not exposed to the network. Scrape them from an agent on the VM, or reach them from your workstation over an SSH tunnel:
ssh -L 9601:127.0.0.1:9601 azureuser@<vm-ip>
Then browse to http://127.0.0.1:9601/metrics locally. On the VM itself:
curl -s http://127.0.0.1:9601/metrics | head -5
Server Components
| Component | Path | Purpose |
|---|---|---|
| Server binary | /usr/local/bin/sockudo |
The Sockudo 4.7.0 release binary |
| Configuration | /etc/sockudo/config.toml |
Drivers, listeners and limits. Ships with no application |
| Per VM application | /etc/sockudo/sockudo.env |
App id, key and secret, mode 0640 root:sockudo |
| TLS material | /etc/sockudo/tls/ |
Certificate and key generated for this VM |
| Bootstrap marker | /var/lib/sockudo/.bootstrap-ready |
Written by first boot; the server will not start without it |
| Credential guard | /usr/local/sbin/sockudo-secret-guard.sh |
Runs before the server opens its socket |
| Round trip check | /usr/local/sbin/sockudo-roundtrip-check.sh |
End to end proof |
| Probe | /usr/local/lib/cloudimg/sockudo-venv |
Isolated Python WebSocket client used by the check |
| nginx site | /etc/nginx/sites-available/sockudo |
TLS termination and WebSocket upgrade |
Filesystem Layout
| Mount | Size | Purpose |
|---|---|---|
/ |
29 GB | Root filesystem |
/boot/efi |
100 MB | UEFI boot partition (Gen2 Hyper V) |
/mnt |
varies | Azure temporary resource disk |
Key directories:
| Path | Purpose |
|---|---|
/etc/sockudo |
Configuration, per VM application and TLS material |
/var/lib/sockudo |
Server working directory and bootstrap marker |
/var/log/sockudo |
Server log, rotated daily or at 64 MB |
/stage/scripts |
First boot OS update helper and its log |
Managing the service
sudo systemctl status sockudo.service --no-pager
sudo systemctl restart sockudo.service
sudo systemctl reload nginx
A restart takes about thirty seconds. That is not a stall: Sockudo drains live WebSocket connections over a thirty second grace period so clients are not cut mid message.
To change configuration, edit /etc/sockudo/config.toml and restart. A configuration that fails to parse does not stop the server — Sockudo logs the failure and carries on with its compiled defaults, which would put the listener on 0.0.0.0 and the queue driver back on Redis. Always confirm the reload was accepted:
sudo tail -5 /var/log/sockudo/sockudo.log
Scripts and Log Files
| File | Purpose |
|---|---|
/var/log/sockudo/sockudo.log |
Server log. Rotated daily or at 64 MB, 7 generations kept |
/var/log/cloudimg-firstboot.log |
First boot record |
/stage/scripts/initial_boot_update.sh |
Applies outstanding OS security updates on first boot |
/stage/scripts/initial_boot_update.log |
Output of the above |
On Startup
Every cloudimg image runs /stage/scripts/initial_boot_update.sh once at first boot, from a root @reboot cron entry, to apply any operating system security updates released since the image was published. Its output goes to /stage/scripts/initial_boot_update.log. To disable it, remove the entry with sudo crontab -e.
Unattended security upgrades remain enabled on the running machine exactly as they are on stock Ubuntu.
Installing your own TLS certificate
Replace the two files and reload nginx. Nothing else changes.
sudo install -o root -g sockudo -m 0644 /etc/letsencrypt/live/<your-domain>/fullchain.pem /etc/sockudo/tls/sockudo.crt
sudo install -o root -g sockudo -m 0640 /etc/letsencrypt/live/<your-domain>/privkey.pem /etc/sockudo/tls/sockudo.key
sudo nginx -t && sudo systemctl reload nginx
If you point a DNS name at the VM, a certificate from a public authority means browser clients connect with no warning and no trust configuration.
Rotating your application credentials
Rotation is shipped as a command rather than a hand edit, because editing the environment file on its own leaves the credentials note and the login banner describing a secret that no longer works. The helper updates all of them together, restarts the server, and then proves the new credential completes a real publish and subscribe round trip before it reports success.
sudo /usr/local/sbin/sockudo-rotate-credentials.sh
Expected output ends with:
OK rotation complete - update your server side publishers with the new secret
That rotates the secret only, which is the common case: your server side publishers need the new value, but connected browser clients are unaffected because they authenticate with the key. Add --all to rotate the key as well, which additionally requires updating every browser client:
sudo /usr/local/sbin/sockudo-rotate-credentials.sh --all
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
sockudo.service is inactive and never started |
First boot has not completed, so the bootstrap marker does not exist | sudo journalctl -u sockudo-firstboot.service and read the failure. The server is designed not to start without credentials |
Server exits immediately, log says Address already in use |
Something else holds 6001 or 9601 | sudo ss -tlnp \| grep -E '6001\|9601' and stop the other process |
| Server starts but refuses every client | The credential guard aborted, or the configuration failed to parse and defaults were used | sudo tail -20 /var/log/sockudo/sockudo.log |
Publishing returns 401 |
Signature mismatch. Almost always the query string used for the signature differs from the one sent, or body_md5 is missing on a non empty body |
Recompute the signature over the exact sorted query string |
Publishing returns 401 with a correct signature |
auth_timestamp is more than 600 seconds from server time |
Check clock skew with timedatectl |
| Browser WebSocket fails immediately | The self signed certificate is not trusted | Install your own certificate, or visit https://your-vm-address/live once and accept it |
/up returns 503 ERROR |
No application is configured | Confirm /etc/sockudo/sockudo.env exists and has all three values |
| Connections drop after two minutes idle | Pusher clients must answer pusher:ping; the activity timeout is 120 seconds |
Use a maintained Pusher client library, which handles this |
Useful diagnostics:
sudo systemctl status sockudo.service --no-pager | head -12
sudo ss -tlnp | grep -E '6001|9601|:443'
sudo tail -20 /var/log/sockudo/sockudo.log
Security Recommendations
- Restrict SSH. Allow TCP 22 from your management network only.
- Install a trusted certificate. The shipped certificate is self signed and generated for this VM. Replace it rather than disabling verification in clients.
- Keep the secret server side. The app key belongs in the browser; the app secret never does.
- Leave the listeners where they are. Sockudo on loopback behind nginx, and metrics on loopback, are what keep the unauthenticated surfaces unreachable.
- Restrict 443 where you can. If only your own backend and known origins need realtime, scope the network security rule to them.
- Rotate on exposure. Rolling the secret is one command and a restart.
- Leave unattended upgrades enabled so the operating system keeps receiving security updates.
Support
cloudimg provides 24/7 support for this image.
- Email: support@cloudimg.co.uk
- Web: www.cloudimg.co.uk
Sockudo itself is an open source project. Its source, issue tracker and protocol documentation are at github.com/sockudo/sockudo.