deepstream
Overview
deepstream.io is an open-source realtime data sync and publish/subscribe server. This guide covers deploying and configuring deepstream.io on Ubuntu 24.04 on Azure, using SecureAnyCloud Azure Marketplace images. Clients hold a single websocket connection and use four primitives over it:
- Records are JSON documents that synchronise automatically, so every subscriber sees a change the moment it is written.
- Events are a straightforward publish and subscribe channel for fire-and-forget messages.
- RPCs route a request to whichever client registered to answer it, and return the result.
- 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. It runs 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. Upstream's sample user file also seeds a published example account. This image ships neither.
The image 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. The file auth handler rejects any username it does not find, so an instance fails 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. The image therefore 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 you get:
- 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 that handles 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-check, suitable for Azure health probes and load balancers deepstream-selftest, a two-connection pub/sub and authentication proof that you can run at any time@deepstream/clientinstalled, so the self test and your own scripts work on the VM immediately
Prerequisites
- An active Azure subscription, an SSH public key, and a VNet plus subnet in the target region
- A subscription to the deepstream.io listing on Azure Marketplace
- Network security group rules that allow 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. Size RAM to your working set, and step up to a compute-optimised size for high connection counts.
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. - In the network security group, allow inbound TCP 22 from your admin network and TCP 80 from your application network. Then create the VM.
Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:deepstream-ubuntu-24-04:default:latest
az vm create \
--resource-group my-rg \
--name deepstream-01 \
--image sac1647283583153:deepstream-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku StandardFirst boot
On the first boot of every VM, deepstream-firstboot.service generates a random administrator password unique to that instance. It hashes the password using deepstream's own file auth settings, writes the hash 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. 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.
Confirm the server is running
SSH in as azureuser. Confirm that both services are active, check the version, and see where each service is 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.
Retrieve your per-VM credentials
The administrator password is unique to this VM, and only root can read it:
sudo cat /root/deepstream-credentials.txtThe 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 the file as sensitive.
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. Query it with:
curl -s -o /dev/null -w 'health-check -> HTTP %{http_code}\n' http://127.0.0.1/health-checkYou should see HTTP 200. This endpoint deliberately reveals nothing about your data.
Prove that strangers are refused
Run this check before you put anything real on the server. It runs four probes. The correct credential must be accepted. This positive control stops a server that is simply down from passing as a secure one. A wrong password, an unknown username and a wholly unauthenticated connection must each be refused:
sudo deepstream-selftest authzAll four lines must report PASS, and the command must exit 0.
Prove that messages actually flow
This check opens two independent authenticated connections. It subscribes the first connection to an event and a record, publishes both from the second, and requires the first to receive them with a matching nonce:
sudo deepstream-selftest pubsubA matching nonce on both lines proves that the broker routed a real payload between two separate clients. It is not merely a sign that a port was open.
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"} ] }'The expected output is 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 401Connect 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/clientconst { 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.
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'The command prints a salted digest. Add the digest to /etc/deepstream/users.yml, following the shape of the existing entry. Then restart the server so that it loads the new user database:
# Add to /etc/deepstream/users.yml:
# reporting-service:
# password: "<the digest printed above>"
# serverData:
# role: service
# then apply:
sudo systemctl restart deepstreamNote that deepstream 10.1.4 silently ignores the -c / --config flag because of a regression in a bundled dependency. The DEEPSTREAM_CONFIG_DIRECTORY environment variable selects the config file instead.
That variable is already set system-wide on this image, which is why the plain deepstream hash command above works correctly.
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 the server. This example is illustrative, so 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 deepstreamDurability 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. Uncomment the cache and storage blocks in /etc/deepstream/config.yml and point them at Redis and MongoDB respectively, then restart. Clustering multiple deepstream nodes also uses Redis. Both are external services that you run and secure yourself.
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 you send credentials over an untrusted network. The websocket login sends the password to the server, so put a certificate on the proxy. For anything beyond a private network, connect with
wss://rather thanws://. - Rotate the shipped admin credential once you have created per-application accounts. Give each application its own user rather than sharing
admin. - Tighten
/etc/deepstream/permissions.ymlso that each account can touch only 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.
Support and licensing
deepstream.io is free and open-source software distributed under the MIT License. This image bundles deepstream.io unmodified, and the MIT License governs your use of it. When this image was built, the project was actively maintained, with version 10.1.4 released on 30 June 2026.
SecureAnyCloud 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 sales@secureanycloud.com and our team will help you get up and running.
Deploy on Azure
Launch deepstream.io 10.1.4 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com
Overview
deepstream.io is an open-source realtime data sync and publish/subscribe server. This guide covers deploying and configuring deepstream.io on AWS, using SecureAnyCloud's preconfigured Amazon Machine Image (AMI). Clients hold a single websocket connection and use four primitives over it:
- Records are JSON documents that synchronise automatically, so every subscriber sees a change the moment it is written.
- Events are a straightforward publish and subscribe channel for fire-and-forget messages.
- RPCs route a request to whichever client registered to answer it, and return the result.
- 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. It runs 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. Upstream's sample user file also seeds a published example account. This image ships neither.
The image 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 instance. The file auth handler rejects any username it does not find, so an instance fails 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. The image therefore 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 you get:
- 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-instance admin password generated on first boot and written to a root-only file
- deepstream bound to loopback
127.0.0.1:6020, with nginx on port 80 as the single public listener. nginx reverse proxies the websocket endpoint, the HTTP/JSON API and the health check - A bundled
deepstream-selftesttool that opens two independent connections and proves that a message really travels between them - The MIT LICENSE baked into the image at
/opt/deepstream/LICENSE
Connecting to your instance
Connect over SSH on port 22, using the private key from the key pair you selected at launch. The SSH login user for Ubuntu 24.04 LTS is ubuntu, for example:
ssh -i your-key.pem ubuntu@<public-ip>
Prerequisites
- An AWS account and a subscription to the deepstream.io listing on AWS Marketplace
- An EC2 key pair in your target region for SSH access
- A security group that allows inbound TCP 22 from your admin network, and TCP 80 from the networks your application clients connect from. deepstream binds to loopback and nginx on port 80 is the only public listener, so port 80 is where your realtime clients connect
Recommended instance type: m5.large (2 vCPU, 8 GB RAM) comfortably handles development and modest production workloads.
deepstream holds record state in memory. Size RAM to your working set, and step up to a compute-optimised type for high connection counts.
Launch from the AWS Marketplace
- Open the deepstream.io listing on AWS Marketplace. Choose Continue to Subscribe, then Continue to Configuration, then Continue to Launch.
- Choose the
m5.largeinstance type, your VPC and subnet, and your EC2 key pair. - Configure the security group to allow inbound TCP 22 from your admin network and TCP 80 from your application network. Then launch.
Launch from the AWS CLI
aws ec2 run-instances \
--image-id <deepstream-ami-id> \
--instance-type m5.large \
--key-name your-key \
--security-group-ids sg-xxxxxxxx \
--subnet-id subnet-xxxxxxxx \
--associate-public-ip-addressReplace the AMI ID with the one shown on your Marketplace subscription for your region.
First boot
On the first boot of every instance, deepstream-firstboot.service generates a random administrator password unique to that instance. It hashes the password using deepstream's own file auth settings, writes the hash 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. 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.
Confirm the server is running
SSH in as ubuntu. Confirm that both services are active, check the version, and see where each service is 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.
Retrieve your per-instance credentials
The administrator password is unique to this instance, and only root can read it:
sudo cat /root/deepstream-credentials.txtThe 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 the file as sensitive.
Check liveness
/health-check is unauthenticated and reports only whether the server is alive. It is the right target for an Elastic Load Balancing health check. Query it with:
curl -s -o /dev/null -w 'health-check -> HTTP %{http_code}\n' http://127.0.0.1/health-checkYou should see HTTP 200. This endpoint deliberately reveals nothing about your data.
Prove that strangers are refused
Run this check before you put anything real on the server. It runs four probes. The correct credential must be accepted. This positive control stops a server that is simply down from passing as a secure one. A wrong password, an unknown username and a wholly unauthenticated connection must each be refused:
sudo deepstream-selftest authzAll four lines must report PASS, and the command must exit 0.
Prove that messages actually flow
This check opens two independent authenticated connections. It subscribes the first connection to an event and a record, publishes both from the second, and requires the first to receive them with a matching nonce:
sudo deepstream-selftest pubsubA matching nonce on both lines proves that the broker routed a real payload between two separate clients. It is not merely a sign that a port was open.
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"} ] }'The expected output is 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 401Connect from your own application
Install the client in your project and connect to the instance's public address on port 80. Replace <public-ip> with your instance's address, and use the password from Step 5:
npm install @deepstream/clientconst { DeepstreamClient } = require('@deepstream/client') const client = new DeepstreamClient('ws://<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.
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'The command prints a salted digest. Add the digest to /etc/deepstream/users.yml, following the shape of the existing entry. Then restart the server so that it loads the new user database:
# Add to /etc/deepstream/users.yml:
# reporting-service:
# password: "<the digest printed above>"
# serverData:
# role: service
# then apply:
sudo systemctl restart deepstreamNote that deepstream 10.1.4 silently ignores the -c / --config flag because of a regression in a bundled dependency. The DEEPSTREAM_CONFIG_DIRECTORY environment variable and a default path symlink select the config file instead.
Both are already set on this image, which is why the plain deepstream hash command above works correctly.
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 the server. This example is illustrative, so 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 deepstreamDurability 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. Uncomment the cache and storage blocks in /etc/deepstream/config.yml and point them at Redis and MongoDB respectively, then restart. Clustering multiple deepstream nodes also uses Redis. Both are external services that you run and secure yourself.
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 security group is your access boundary. The shipped default security group opens only port 22.
- Terminate TLS in front of the server before you send credentials over an untrusted network. The websocket login sends the password to the server, so put a certificate on the proxy or an Application Load Balancer. For anything beyond a private network, connect with
wss://rather thanws://. - Rotate the shipped admin credential once you have created per-application accounts. Give each application its own user rather than sharing
admin. - Tighten
/etc/deepstream/permissions.ymlso that each account can touch only 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.
Support and licensing
deepstream.io is free and open-source software distributed under the MIT License. This image bundles it unmodified, with the MIT LICENSE baked in at /opt/deepstream/LICENSE, and the MIT License governs your use of it. When this image was built, the project was actively maintained, with version 10.1.4 released on 30 June 2026.
SecureAnyCloud images include 24/7 support. If you need help deploying or configuring deepstream.io on AWS, contact us.
Deploy on AWS
Find this image on AWS Marketplace and deploy in minutes.
Need Help?
Email sales@secureanycloud.com and our team will help you get up and running.
Deploy on AWS
Launch deepstream.io on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com