So
Streaming & Messaging Azure

Sockudo 4.7.0 on Ubuntu 24.04 on Azure User Guide

| Product: Sockudo 4.7.0 on Ubuntu 24.04 on Azure

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.toml has no application definition, so out of the box the server cannot authenticate anybody.

  • Your VM generates its own application on first boot. sockudo-firstboot.service creates an application id, a 32 character key and a 48 character secret from openssl on your machine, and writes them to /etc/sockudo/sockudo.env (mode 0640, 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.service carries ConditionPathExists on a bootstrap marker that first boot writes only after the credentials exist. This is a condition rather than an ordering rule on purpose: an After= 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:9601 with no authentication. Here it is bound to loopback, and the unauthenticated /usage and /stats introspection 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

  1. An active Azure subscription.
  2. Permission to create virtual machines and network security rules.
  3. An SSH key pair for azureuser.
  4. A network security rule allowing inbound TCP 443 from the clients that need realtime, and TCP 22 from your management network only.
  5. 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

  1. In the Azure Portal choose Create a resource and search the Marketplace for Sockudo on Ubuntu 24.04 LTS by cloudimg.
  2. Select the offer and choose Create.
  3. Under Basics, pick your subscription, resource group and region, and set the VM size to Standard_B2s.
  4. Set the authentication type to SSH public key and the username to azureuser.
  5. Under Inbound port rules, allow HTTPS (443) and SSH (22).
  6. 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

The three services that make up the appliance all report active after first boot

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 credentials note records the per VM application and the URLs clients connect to

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 signed publish returns ok true, and an unsigned one is refused

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

The shipped round trip check proves delivery, then proves the refusals, then re confirms the real credential

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

  1. Restrict SSH. Allow TCP 22 from your management network only.
  2. Install a trusted certificate. The shipped certificate is self signed and generated for this VM. Replace it rather than disabling verification in clients.
  3. Keep the secret server side. The app key belongs in the browser; the app secret never does.
  4. Leave the listeners where they are. Sockudo on loopback behind nginx, and metrics on loopback, are what keep the unauthenticated surfaces unreachable.
  5. Restrict 443 where you can. If only your own backend and known origins need realtime, scope the network security rule to them.
  6. Rotate on exposure. Rolling the secret is one command and a restart.
  7. Leave unattended upgrades enabled so the operating system keeps receiving security updates.

Support

cloudimg provides 24/7 support for this image.

Sockudo itself is an open source project. Its source, issue tracker and protocol documentation are at github.com/sockudo/sockudo.