deepstream.io on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of deepstream.io on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. deepstream.io is an open source realtime data sync and publish/subscribe server. Clients hold a single websocket connection and use four primitives over it:
- records — JSON documents that synchronise automatically, so every subscriber sees a change the moment it is written
- events — a straightforward publish and subscribe channel for fire and forget messages
- RPCs — a request is routed to whichever client registered to answer it, and the result is returned
- presence — reports which clients are currently connected
The image installs deepstream.io 10.1.4 (the @deepstream/server package) on Node.js 22 LTS from the official NodeSource repository, and runs it under systemd as deepstream.service. deepstream is a headless server: there is no web interface and no dashboard to log into. You interact with it from your own application code, from the HTTP API, and from the command line.
Secure by default — this is the main difference from a stock install. Upstream's sample configuration sets auth: type: none, which means any client that can reach the port may read and write every record, event and RPC, and upstream's sample user file seeds a published example account. This image ships neither. It is pinned to file based authentication with salted password hashes, the user database ships empty, and a unique administrator password is generated on the first boot of every VM. Because the file auth handler rejects any username it does not find, an instance is fail closed: it refuses every client until first boot has provisioned a credential.
Proof that it works, not just that it is up. A realtime broker that answers a health check but never delivers a message is functionally dead, so the image ships deepstream-selftest, which opens two independent authenticated connections, publishes from one and requires the other to actually receive the payload. It also runs four authentication probes, including a positive control.
What is included:
-
deepstream.io 10.1.4 from the official
@deepstream/servernpm package on Node.js 22 LTS, run under systemd asdeepstream.service -
File based authentication replacing upstream's open default, with a per VM admin password generated on first boot and written to a root only file
-
A reverse proxy on port 80 handling websocket upgrade, so the realtime endpoint and the HTTP API are reachable on a standard port while deepstream itself binds to loopback only
-
The HTTP and JSON API on
/apifor systems that cannot hold an open websocket -
An unauthenticated liveness endpoint on
/health-checksuitable for Azure health probes and load balancers -
deepstream-selftest, a two connection pub/sub and authentication proof you can run at any time -
@deepstream/clientinstalled so the self test and your own scripts work on the box immediately
Prerequisites
-
Active Azure subscription, SSH public key, VNet + subnet in target region
-
Subscription to the deepstream.io listing on Azure Marketplace
-
Network Security Group rules allowing TCP 22 (admin) and TCP 80 (the realtime websocket endpoint and HTTP API). Open port 80 only to the networks your application clients connect from
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) comfortably handles development and modest production workloads. deepstream holds record state in memory, so size RAM to your working set and step up to a compute optimised size for high connection counts.
Step 1: Deploy from the Azure Portal
- Open the deepstream.io offer on the Azure Marketplace and choose Get It Now, then Create.
- Select your subscription, resource group and region, and choose the
Standard_B2ssize. - Provide your SSH public key for the
azureuseraccount. - Allow inbound TCP 22 from your admin network and TCP 80 from your application network in the Network Security Group, then create the VM.
Step 2: Deploy from the Azure CLI
az vm create \
--resource-group my-rg \
--name deepstream-01 \
--image <deepstream-marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
Step 3: First boot
On the first boot of every VM, deepstream-firstboot.service generates a random administrator password unique to that instance, hashes it using deepstream's own file auth settings, writes it into /etc/deepstream/users.yml, and records the plaintext in /root/deepstream-credentials.txt. Only then does it create the marker that releases deepstream.service to start.
That ordering is deliberate. deepstream.service carries a condition on that marker, so if the first boot step ever failed the server would decline to start rather than come up with an empty user database. First boot completes in a few seconds.
Step 4: Confirm the server is running
SSH in as azureuser and confirm both services are active, check the version, and see where things are listening:
systemctl is-active deepstream.service nginx.service
deepstream --version
ss -tln | grep -E ':6020 |:80 '
You should see active twice and version 10.1.4. Note that deepstream itself is bound to 127.0.0.1:6020 only — the reverse proxy on port 80 is the single public listener.

Step 5: Retrieve your per VM credentials
The administrator password is unique to this VM and is readable only by root:
sudo cat /root/deepstream-credentials.txt
This file gives you the username (admin), the password, and the websocket URL your clients should connect to. Store the password in your own secret manager and treat this file as sensitive.
Step 6: Check liveness
/health-check is unauthenticated and reports only whether the server is alive. It is the right target for an Azure health probe or a load balancer:
curl -s -o /dev/null -w 'health-check -> HTTP %{http_code}\n' http://127.0.0.1/health-check
You should see HTTP 200. This endpoint deliberately reveals nothing about your data.
Step 7: Prove that strangers are refused
This is the check worth running before you put anything real on the server. It runs four probes: the correct credential must be accepted (a positive control, so that a server which is simply down cannot masquerade as a secure one), then a wrong password, an unknown username and a wholly unauthenticated connection must each be refused:
sudo deepstream-selftest authz
All four lines must report PASS and the command must exit 0.

Step 8: Prove that messages actually flow
This opens two independent authenticated connections, subscribes the first to an event and a record, publishes both from the second, and requires the first to genuinely receive them with a matching nonce:
sudo deepstream-selftest pubsub
A matching nonce on both lines is proof that the broker routed a real payload between two separate clients, rather than merely that a port was open.

Step 9: Use the HTTP and JSON API
Applications that cannot hold an open websocket can publish and read over HTTP. Every request carries authData, so the same credential gates this transport too. Write a value into a record:
curl -s -X POST http://127.0.0.1/api -H 'Content-Type: application/json' -d '{
"authData": {"username":"admin","password":"<DEEPSTREAM_ADMIN_PASSWORD>"},
"body": [ {"topic":"record","action":"write","recordName":"guide/demo","path":"status","data":"ok"} ]
}'
Expected output:
{"result":"SUCCESS","body":[{"success":true}]}
Now read it back:
curl -s -X POST http://127.0.0.1/api -H 'Content-Type: application/json' -d '{
"authData": {"username":"admin","password":"<DEEPSTREAM_ADMIN_PASSWORD>"},
"body": [ {"topic":"record","action":"read","recordName":"guide/demo"} ]
}'
Expected output — the value you just wrote, with its version:
{"result":"SUCCESS","body":[{"version":1,"data":{"status":"ok"},"success":true}]}
The same request without credentials is rejected outright:
curl -s -o /dev/null -w 'no credentials -> HTTP %{http_code}\n' -X POST http://127.0.0.1/api \
-H 'Content-Type: application/json' \
-d '{"body":[{"topic":"record","action":"read","recordName":"guide/demo"}]}'
Expected output:
no credentials -> HTTP 401

Step 10: Connect from your own application
Install the client in your project and connect to the VM's public address on port 80. Replace <vm-public-ip> with your VM's address and use the password from Step 5:
npm install @deepstream/client
const { DeepstreamClient } = require('@deepstream/client')
const client = new DeepstreamClient('ws://<vm-public-ip>/deepstream')
await client.login({ username: 'admin', password: '<your-password>' })
// Events: fire and forget publish/subscribe
client.event.subscribe('telemetry/temperature', (data) => console.log(data))
client.event.emit('telemetry/temperature', { celsius: 21.5 })
// Records: a JSON document that stays in sync across every subscriber
const record = client.record.getRecord('devices/thermostat-1')
record.subscribe((data) => console.log('thermostat changed:', data))
await client.record.setDataWithAck('devices/thermostat-1', { celsius: 21.5 })
Any client that connects without valid credentials is disconnected before it can read or write anything.
Step 11: Add more users
The image ships a single admin account. To add another user, generate a password hash and add it to the user database:
sudo deepstream hash 'your-new-password'
That prints a salted digest. Add it to /etc/deepstream/users.yml following the existing entry's shape, then restart the server so the new user database is loaded:
# Add to /etc/deepstream/users.yml:
# reporting-service:
# password: "<the digest printed above>"
# serverData:
# role: service
# then apply:
sudo systemctl restart deepstream
Note that deepstream 10.1.4 silently ignores the -c / --config flag because of a regression in a bundled dependency, so the config file is selected by the DEEPSTREAM_CONFIG_DIRECTORY environment variable instead. That variable is already set system wide on this image, which is why the plain deepstream hash command above works correctly.
Step 12: Restrict what each user may do
Every authenticated user currently has full access to all records, events and RPCs. To grant narrower rights per user or per name pattern, edit the rules in /etc/deepstream/permissions.yml and restart. This example is illustrative — adapt the patterns to your data model:
# In /etc/deepstream/permissions.yml, replace the "*" record rule with
# something narrower, for example:
# record:
# "devices/$deviceId":
# read: true
# write: "user.data.role === 'admin'"
# then apply:
sudo systemctl restart deepstream
Step 13: Durability and scaling
By default deepstream keeps record data in memory, so records do not survive a restart. That is the right default for presence, telemetry and ephemeral collaborative state. If you need records to persist, enable a cache and a storage backend by uncommenting the cache and storage blocks in /etc/deepstream/config.yml and pointing them at Redis and MongoDB respectively, then restart. Clustering multiple deepstream nodes also uses Redis. Both are external services you run and secure yourself.
Step 14: Security recommendations
- Open port 80 only to the networks your clients connect from. deepstream is bound to loopback and the reverse proxy is the only public listener, so the Network Security Group is your access boundary.
- Terminate TLS in front of the server before sending credentials over an untrusted network. The websocket login sends the password to the server, so put a certificate on the proxy and connect with
wss://rather thanws://for anything beyond a private network. - Rotate the shipped admin credential once you have created per application accounts, and give each application its own user rather than sharing
admin. - Tighten
/etc/deepstream/permissions.ymlso each account can only touch the records and events it needs. The default grants every authenticated user full access. - Keep
/root/deepstream-credentials.txtrestricted to root, and copy the password into your own secret manager. - Keep the OS patched. The image ships fully updated and with unattended security upgrades enabled.
Step 15: Support and Licensing
deepstream.io is free and open source software distributed under the MIT License. This image bundles deepstream.io unmodified; your use of deepstream.io is governed by its license. At the time this image was built the project was actively maintained, with version 10.1.4 released on 30 June 2026.
cloudimg images include 24/7 support. If you need help deploying or configuring deepstream.io on Azure, contact us.
Deploy on Azure
Find this image on the Azure Marketplace and deploy in minutes.
Need Help?
Email support@cloudimg.co.uk and our team will help you get up and running.