Deployment guide

Nchan

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. 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. Your application never has to keep a real-time connection open.

This SecureAnyCloud image gives you a complete, working message server rather than a module you still have to compile and configure. Nchan endpoints are unauthenticated by default, which 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 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 you get:

  • nginx 1.24.0 with the Nchan module (libnginx-mod-nchan 1.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 set 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, and a health endpoint.
  • nchan-firstboot.service, which generates this VM's own credential on first boot.
  • nchan-selftest, a bundled command that proves message delivery across all three transports.
  • nchan-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 SecureAnyCloud 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.
  • A resource group in your target region.
  • A VM size of Standard_B2s or larger.

Nchan is extremely light on CPU and memory. Connection count, not message rate, normally drives sizing.

1

Deploy from the Azure Portal

  • Open the Nchan on Ubuntu 24.04 LTS offer on the Azure Marketplace. Choose Get It Now, then Create.
  • Select your subscription, resource group and region, and choose the Standard_B2s size.
  • Provide your SSH public key for the azureuser account.
  • 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.

2

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:nchan:default:latest
az vm create \
  --resource-group my-rg \
  --name nchan-01 \
  --image sac1647283583153:nchan:default:latest \
  --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
3

Connect via SSH

Look up the VM's public IP, then connect as azureuser:

az vm show -d --resource-group my-rg --name nchan-01 --query publicIps -o tsv ssh azureuser@<public-ip>
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 is reachable only from the VM itself. Query it to confirm the module is live and to see the running Nchan version:

curl -s http://127.0.0.1/nchan_stub_status
5

Get This VM's Credential

There is no default password. On first boot, nchan-firstboot.service generated a credential unique to this VM. It wrote the credential into an nginx password file and recorded it in a root-only note. Read the note:

sudo cat /root/nchan-info.txt

Expected output. Your values will differ, because every VM gets its own:

NCHAN_PUBLISH_USER=publisher NCHAN_PUBLISH_PASSWORD=<set-at-first-boot> 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 in the following steps read it straight from that file, so you never have to paste it.

6

Publish a Message

Publish to a channel called demo. You do not need to create the channel first, because 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

Nchan returns the channel's state. The line 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 confirm the endpoint is 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
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.

The subscriber endpoint is configured with nchan_subscriber_first_message oldest. A subscriber that connects after a message was published therefore still receives it from the buffer, and you do not need to time the two commands.

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
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 the command uses --max-time 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. The browser EventSource constructor cannot send an Authorization header. If you plan to subscribe directly from a browser, see Step 13 for the two supported ways to handle that.

9

Receive the Message over WebSocket

The image ships nchan-ws-recv, a WebSocket subscriber written entirely against the Python standard library. 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 point of Nchan: one publish, delivered unchanged to subscribers on three different transports, with the client choosing the transport.

10

Inspect Channel State

A GET on the publisher endpoint returns the channel's current state. Use it to check retention and to 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 the channel:

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
11

Run the Built In Self Test

The image ships a self-test that performs the whole round trip end to end. It publishes a unique message, receives it over all three transports, and confirms that anonymous publishing, anonymous subscribing and a wrong password are all refused.

Run it whenever you want to confirm the VM is delivering messages:

sudo /usr/local/sbin/nchan-selftest

Expected output:

OK per-VM credential publishes nchan-nonce-... to sac-selftest-... and it is received over long-poll + EventSource + WebSocket; anon publish/subscribe and wrong password all rejected 401
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. 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 reach a consumer that is currently offline, put a durable store in front of Nchan or use a broker designed for that.

To change the limits, edit the publisher location in /etc/nginx/sites-available/sac-nchan and reload nginx:

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
13

The Security Model

Nchan itself has no concept of authentication. The nginx configuration this image ships provides everything below.

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. Leaving it open is never 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 can relax it. Make that a deliberate decision, not a default you 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. Make either change in the subscriber location in /etc/nginx/sites-available/sac-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.

14

Rotate the Credential

The credential is an ordinary nginx password file, so rotation is a one-line command. It prompts you for the new password twice.

sudo htpasswd -B /etc/nginx/sac-nchan.htpasswd publisher
sudo systemctl reload nginx

Afterwards, update /root/nchan-info.txt, or replace that note with your own secret management.

15

Managing the Service

systemctl status nginx --no-pager | head -5

After a configuration change, reload with sudo systemctl reload nginx. This drops no connections. Restart with sudo systemctl restart nginx only when you must, because it drops connections and clears every channel.

16

Add TLS

Port 80 carries both your messages and the basic authentication credential in clear text, so terminate TLS before you run Nchan in production.

You can put an Azure Application Gateway with a certificate in front of port 80. Alternatively, terminate TLS on the VM itself. This is a stock nginx, so 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 publish with https://.

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. A message published on one node then reaches subscribers connected to another.

This image deliberately ships no Redis. It uses Nchan's in-memory store, which is the right choice for a single VM and keeps every component of the image permissively licensed.

If you add Redis yourself, note 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-server package (7.0.15, BSD 3-Clause)
  • The redis:7.2-alpine container image (BSD 3-Clause)
  • Valkey, the openly governed fork (valkey/valkey:8-alpine, BSD 3-Clause)

To point Nchan at it, add an nchan_redis_server directive to the publisher and subscriber locations, then reload nginx. With Redis in place, channel state becomes shared and persistent, so review your retention settings at the same time.

18

Versions and Project Maintenance

This image ships the Nchan version packaged by Ubuntu 24.04 LTS, 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. 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, weigh that trade-off before you adopt Nchan.

If you 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. This takes the module outside Ubuntu's security maintenance, and you become responsible for rebuilding it whenever nginx's module ABI changes. We recommend staying on the packaged version unless you need a specific fix.

19

Support and Licensing

Nchan is distributed under the MIT Licence and nginx under the 2-clause BSD Licence. Both are permissive, so you may use, modify and redistribute them commercially. There are no licence keys, no seat counts and no usage reporting.

SecureAnyCloud 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 SecureAnyCloud catalogue at secureanycloud.com.

Need Help?

Contact SecureAnyCloud support at sales@secureanycloud.com.

Deploy on Azure

Launch Nchan 1.3 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

Deploy on Azure