Nchan Pub/Sub Message Server on Ubuntu 24.04 on Azure User Guide
Overview
Nchan is an open source publish and subscribe server built as a module for nginx. Your application publishes a message to a named channel with an ordinary HTTP request, and Nchan delivers it immediately to every client subscribed to that channel, over WebSocket, EventSource (also known as Server Sent Events) or long polling, whichever the client asks for. Because the delivery layer is nginx itself, a single modest VM holds a very large number of idle connections, and your application never has to keep a real time connection open.
The cloudimg image gives you a complete, working message server rather than a module you still have to compile and configure. A publisher endpoint, a subscriber endpoint serving all three transports, a loopback only statistics endpoint and a health endpoint are already in place, with retention limits set deliberately.
Nchan endpoints are unauthenticated by default. That default is not safe on a public network: an open publisher endpoint lets anyone inject messages into any channel, and an open subscriber endpoint leaks every message on a channel to anyone who can guess its name. This image therefore puts both endpoints behind HTTP basic authentication, using a credential generated uniquely on each VM's first boot and never baked into the image.
What is included:
- nginx 1.24.0 with the Nchan module (
libnginx-mod-nchan1.3.6), both installed as standard Ubuntu packages - A publisher endpoint at
/pub/<channel>and a subscriber endpoint at/sub/<channel>, both authenticated - All three transports (WebSocket, EventSource and long polling) served from the one subscriber endpoint
- Retention configured at 100 messages per channel for up to one hour, with late subscribers receiving the backlog
- A loopback only statistics endpoint at
/nchan_stub_status nchan-firstboot.service, generating this VM's own credential on first bootnchan-selftest, a bundled command that proves message delivery across all three transportsnchan-ws-recv, a dependency free WebSocket subscriber written against the Python standard library- Ubuntu 24.04 LTS base, latest patches, unattended security upgrades enabled
- 24/7 cloudimg support
Key facts:
| Platform | Microsoft Azure (Gen2 VM image) |
| Base image | Ubuntu 24.04 LTS |
| Default login user | azureuser |
| Recommended size | Standard_B2s or larger |
| Open ports | TCP 22 (SSH), TCP 80 (publisher and subscriber endpoints) |
| Credential file | /root/nchan-info.txt (generated per VM at first boot) |
Prerequisites
An active Azure subscription, an SSH key pair for the azureuser account, and a resource group in your target region. Recommended size: Standard_B2s or larger. Nchan is extremely light on CPU and memory; connection count, not message rate, is normally what drives sizing.
Step 1: Deploy from the Azure Portal
- Open the Nchan on Ubuntu 24.04 LTS 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 (SSH) from your admin network and TCP 80 (the publisher and subscriber endpoints) from your client networks only, then create the VM.
Put a TLS reverse proxy or an Azure Application Gateway in front of port 80 before you carry anything sensitive; see Step 16.
Step 2: Deploy from the Azure CLI
az vm create \
--resource-group my-rg \
--name nchan-01 \
--image <nchan-marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
# After the VM is created, open the two ports to your own networks only
az vm open-port --resource-group my-rg --name nchan-01 --port 22 --priority 1001
az vm open-port --resource-group my-rg --name nchan-01 --port 80 --priority 1002
Step 3: Connect via SSH
Find the public IP and connect as azureuser:
az vm show -d --resource-group my-rg --name nchan-01 --query publicIps -o tsv
ssh azureuser@<public-ip>
Step 4: Verify the Service
Check that nginx is running and that the Nchan module is actually loaded into it.
systemctl is-active nginx.service
curl -s http://127.0.0.1/health
Expected output:
active
ok
Confirm the module is loaded and note its exact version. This is the package that provides Nchan, and it is kept current by ordinary Ubuntu security updates:
dpkg-query -W -f='${Package} ${Version}\n' nginx libnginx-mod-nchan
Expected output:
nginx 1.24.0-2ubuntu7.15
libnginx-mod-nchan 1:1.3.6+dfsg-4build1
The statistics endpoint, reachable only from the VM itself, confirms the module is live and reports the running Nchan version:
curl -s http://127.0.0.1/nchan_stub_status

Step 5: Get This VM's Credential
There is no default password. On first boot nchan-firstboot.service generated a credential unique to this VM, wrote it into an nginx password file, and recorded it in a root only note.
sudo cat /root/nchan-info.txt
Expected output (your values will differ — every VM gets its own):
NCHAN_PUBLISH_USER=publisher
NCHAN_PUBLISH_PASSWORD=8f2c...
NCHAN_URL=http://<vm-public-ip>/
NCHAN_PUBLISH_ENDPOINT=http://<vm-public-ip>/pub/<channel>
NCHAN_SUBSCRIBE_ENDPOINT=http://<vm-public-ip>/sub/<channel>
The same credential authenticates both the publisher and the subscriber endpoint. The commands below read it straight out of that file, so you never have to paste it.
Step 6: Publish a Message
Publish to a channel called demo. The channel does not need to be created first — publishing to a name brings it into existence.
U=$(sudo grep -m1 '^NCHAN_PUBLISH_USER=' /root/nchan-info.txt | cut -d= -f2-)
P=$(sudo grep -m1 '^NCHAN_PUBLISH_PASSWORD=' /root/nchan-info.txt | cut -d= -f2-)
curl -s -u "$U:$P" -X POST -H 'Content-Type: text/plain' \
--data 'hello from the guide' http://127.0.0.1/pub/demo
Expected output — Nchan returns the channel's state, and queued messages: 1 confirms the message was accepted and buffered:
queued messages: 1
last requested: -1 sec. ago
active subscribers: 0
last message id: 1786571559:0
Now prove the endpoint is genuinely protected. Publishing without the credential must be refused:
curl -s -o /dev/null -w 'anonymous publish -> HTTP %{http_code}\n' \
-X POST --data 'should not work' http://127.0.0.1/pub/demo
Expected output:
anonymous publish -> HTTP 401
Step 7: Receive the Message by Long Polling
Long polling is the simplest transport: an ordinary HTTP GET that returns as soon as a message is available. Because the subscriber endpoint is configured with nchan_subscriber_first_message oldest, a subscriber that connects after a message was published still receives it from the buffer, so this works without any timing games.
U=$(sudo grep -m1 '^NCHAN_PUBLISH_USER=' /root/nchan-info.txt | cut -d= -f2-)
P=$(sudo grep -m1 '^NCHAN_PUBLISH_PASSWORD=' /root/nchan-info.txt | cut -d= -f2-)
curl -s -u "$U:$P" http://127.0.0.1/sub/demo
Expected output — the exact payload published in Step 6:
hello from the guide
Step 8: Receive the Message over EventSource (SSE)
The same URL serves EventSource when the client asks for text/event-stream. This is a streaming connection, so --max-time is used to detach after a few seconds; that timeout is expected and is not an error.
U=$(sudo grep -m1 '^NCHAN_PUBLISH_USER=' /root/nchan-info.txt | cut -d= -f2-)
P=$(sudo grep -m1 '^NCHAN_PUBLISH_PASSWORD=' /root/nchan-info.txt | cut -d= -f2-)
curl -s -N -m 5 -u "$U:$P" -H 'Accept: text/event-stream' http://127.0.0.1/sub/demo || true
Expected output — an SSE comment line that Nchan sends to open the stream, then the event framing carrying the same payload:
: hi
id: 1786571559:0
data: hello from the guide
Browsers reach this endpoint with the native EventSource API. Note that the browser EventSource constructor cannot send an Authorization header, so if you intend to subscribe directly from a browser, see Step 13 for the two supported ways to handle that.
Step 9: Receive the Message over WebSocket
The image ships nchan-ws-recv, a WebSocket subscriber written entirely against the Python standard library, so there is nothing to install.
U=$(sudo grep -m1 '^NCHAN_PUBLISH_USER=' /root/nchan-info.txt | cut -d= -f2-)
P=$(sudo grep -m1 '^NCHAN_PUBLISH_PASSWORD=' /root/nchan-info.txt | cut -d= -f2-)
nchan-ws-recv --url ws://127.0.0.1/sub/demo --user "$U" --password "$P" --count 1 --timeout 15
Expected output — the same payload again, this time delivered over a real WebSocket connection:
hello from the guide
That is the whole point of Nchan: one publish, delivered unchanged to subscribers on three different transports, with the client choosing the transport.
Step 10: Inspect Channel State
A GET on the publisher endpoint returns the channel's current state, which is how you check retention and see who is connected.
U=$(sudo grep -m1 '^NCHAN_PUBLISH_USER=' /root/nchan-info.txt | cut -d= -f2-)
P=$(sudo grep -m1 '^NCHAN_PUBLISH_PASSWORD=' /root/nchan-info.txt | cut -d= -f2-)
curl -s -u "$U:$P" http://127.0.0.1/pub/demo
Expected output:
queued messages: 1
last requested: 1 sec. ago
active subscribers: 0
last message id: 1786571559:0
To discard a channel and everything buffered in it, delete it:
U=$(sudo grep -m1 '^NCHAN_PUBLISH_USER=' /root/nchan-info.txt | cut -d= -f2-)
P=$(sudo grep -m1 '^NCHAN_PUBLISH_PASSWORD=' /root/nchan-info.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'delete channel -> HTTP %{http_code}\n' \
-u "$U:$P" -X DELETE http://127.0.0.1/pub/demo
Expected output:
delete channel -> HTTP 200
Step 11: Run the Built In Self Test
The image ships a self test that performs the whole round trip end to end — publish a unique message, receive it over all three transports, and confirm that anonymous publishing, anonymous subscribing and a wrong password are all refused. Use it whenever you want to confirm the VM is genuinely delivering messages.
sudo /usr/local/sbin/nchan-selftest
Expected output:
OK per-VM credential publishes nchan-nonce-... to cloudimg-selftest-... and it is received over long-poll + EventSource + WebSocket; anon publish/subscribe and wrong password all rejected 401

Step 12: Retention and Limits
Messages are held in memory only. Nothing is written to disk, and restarting nginx clears every channel. The configured limits are:
| Setting | Value | Meaning |
|---|---|---|
nchan_message_buffer_length |
100 | Each channel keeps its 100 most recent messages |
nchan_message_timeout |
1h | A buffered message is discarded after one hour |
nchan_subscriber_first_message |
oldest | A new subscriber receives the buffered backlog, then live messages |
client_max_body_size |
1m | Largest single message that may be published |
A late subscriber receives the buffered backlog, and a GET on the publisher endpoint reports how many messages a channel currently holds:

Nchan is a delivery system, not a durable queue. If a message must survive a restart or must be guaranteed to reach a consumer that is currently offline, put a durable store in front of it or use a broker designed for that.
To change the limits, edit the publisher location in /etc/nginx/sites-available/cloudimg-nchan and reload:
sudo nginx -t && sudo systemctl reload nginx
Expected output:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Step 13: The Security Model
Nchan itself has no concept of authentication. Everything below is provided by the nginx configuration this image ships.
Publisher endpoint — always authenticated. An open publisher endpoint on a reachable address lets anyone inject arbitrary messages into any channel your subscribers are listening to. There is no configuration in which leaving it open is appropriate on an untrusted network.
Subscriber endpoint — authenticated by default in this image. An open subscriber endpoint leaks every message on a channel to anyone who can guess or enumerate the channel name, and channel names are frequently predictable (user-1234, order-5678). If your messages are genuinely public, you may relax it; that is a decision to make deliberately, not a default to inherit.
Statistics endpoint — /nchan_stub_status reports live channel counts and subscriber counts, so it is restricted to the VM itself. From any other address the same URL is refused:
IP=$(hostname -I | awk '{print $1}')
curl -s -o /dev/null -w 'stub status off-loopback -> HTTP %{http_code}\n' "http://$IP/nchan_stub_status"
Expected output:
stub status off-loopback -> HTTP 403

Subscribing from a browser. The browser EventSource and WebSocket APIs cannot set an Authorization header, so basic authentication does not suit browser subscribers. There are two good options, both edited into the subscriber location in /etc/nginx/sites-available/cloudimg-nchan:
- Delegate to your own application with
nchan_authorize_request. Nchan makes a subrequest to your app for each subscribe attempt; your app answers 200 to allow or 403 to deny, using your existing session cookie or token. This is the recommended production pattern.
location ~ "^/sub/(?<nchan_channel>[A-Za-z0-9_.:-]{1,128})$" {
nchan_authorize_request /auth;
nchan_subscriber;
nchan_channel_id $nchan_channel;
nchan_subscriber_first_message oldest;
}
location = /auth {
internal;
proxy_pass http://your-app-backend/nchan-auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
- Use unguessable channel names. Generate a long random channel id per user or per session, hand it to the client over your already authenticated application, and treat the channel name itself as the capability.
Whichever you choose, always keep the publisher endpoint authenticated.
Step 14: Rotate the Credential
The credential is an ordinary nginx password file, so rotation is a one liner. You will be prompted for the new password twice.
sudo htpasswd -B /etc/nginx/cloudimg-nchan.htpasswd publisher
sudo systemctl reload nginx
Remember to update /root/nchan-info.txt afterwards, or replace that note with your own secret management.
Step 15: Managing the Service
systemctl status nginx --no-pager | head -5
Reload after a configuration change with sudo systemctl reload nginx (no dropped connections), and restart with sudo systemctl restart nginx (drops connections and clears every channel).
Step 16: Add TLS
Port 80 carries both your messages and the basic authentication credential in clear text. Before production use, terminate TLS. You can either put an Azure Application Gateway with a certificate in front of port 80, or terminate on the VM itself — because this is a stock nginx, the standard Certbot nginx plugin works unmodified:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d nchan.your-domain.example
Once TLS is in place, subscribe with wss:// rather than ws://, and use https:// for publishing.
Step 17: Scaling and the Optional Redis Backend
A single VM handles a very large number of concurrent subscribers, because idle connections cost nginx very little. If you outgrow one VM, Nchan can share channel state between several nginx nodes through Redis, so a message published on one node reaches subscribers connected to another.
This image deliberately ships no Redis. Nchan's in memory store is used, which is the right choice for a single VM and keeps every component of the image permissively licensed. If you add Redis yourself, be aware that Redis changed its licence at version 7.4 and is no longer open source from that release onward. Choose one of these instead:
- Ubuntu 24.04's own
redis-serverpackage (7.0.15, BSD 3 Clause) - The
redis:7.2-alpinecontainer image (BSD 3 Clause) - Valkey, the openly governed fork (
valkey/valkey:8-alpine, BSD 3 Clause)
Point Nchan at it by adding an nchan_redis_server directive to the publisher and subscriber locations, then reload nginx. Note that with Redis in place, channel state becomes shared and persistent, so review your retention settings at the same time.
Step 18: Versions and Project Maintenance
This image ships the Nchan packaged by Ubuntu 24.04 LTS, version 1.3.6, which receives updates through the ordinary Ubuntu archive. Nothing is pinned or held back, so unattended-upgrades keeps both nginx and the Nchan module current.
Nchan is a mature project with a deliberately slow release cadence, and it is worth being aware of that before you adopt it. The most recent upstream release is version 1.3.8, published in February 2026; the release before it, 1.3.7, was published in September 2024. The project is actively maintained and is not archived, but you should not expect frequent feature releases. For a message delivery layer this stability is often a virtue; if you need a fast moving product with a large feature roadmap, evaluate that trade off deliberately.
If you specifically need upstream 1.3.8, you can build the module from source against the distribution nginx and place the resulting ngx_nchan_module.so alongside the packaged one. Doing so takes the module outside Ubuntu's security maintenance, and you become responsible for rebuilding it whenever nginx's module ABI changes, so we recommend staying on the packaged version unless you need a specific fix.
Step 19: Support and Licensing
Nchan is distributed under the MIT Licence and nginx under the 2 clause BSD Licence. Both are permissive: you may use, modify and redistribute them commercially. There are no licence keys, no seat counts and no usage reporting.
cloudimg provides 24/7 support for this image, covering deployment, configuration and operational questions.
Deploy on Azure
Find Nchan on Ubuntu 24.04 LTS in the Azure Marketplace, or browse the full cloudimg catalogue at cloudimg.co.uk.
Need Help?
Contact cloudimg support at support@cloudimg.co.uk.