Nchan Pub/Sub Message Server on AWS 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 instance holds a very large number of idle connections, and your application never has to keep a real time connection open.
The cloudimg AMI 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 instance'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 instance'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
Prerequisites
An active AWS account, an EC2 key pair in your target region, and a VPC with a subnet. Recommended instance type: m5.large or larger. Nchan is extremely light on CPU and memory; connection count, not message rate, is normally what drives sizing.
Step 1: Launch from the AWS Marketplace (Console)
Find Nchan Pub/Sub Message Server in the AWS Marketplace and choose Continue to Subscribe, then Continue to Configuration and Continue to Launch. Pick an m5.large (or larger) instance type. In the network step attach a security group that allows inbound TCP 22 (SSH) and TCP 80 (the publisher and subscriber endpoints) from your client networks only. Put a TLS reverse proxy or an Application Load Balancer in front of port 80 before you carry anything sensitive.
Step 2: Launch from the AWS CLI
REGION="us-east-1"
AMI_ID="<the-nchan-ami-id-from-the-listing>"
KEY_NAME="<your-ec2-key-pair>"
SUBNET_ID="<your-subnet-id>"
# A security group that opens SSH and the nginx port to your own networks only
SG_ID=$(aws ec2 create-security-group --region "$REGION" \
--group-name nchan-sg --description "Nchan pub/sub" \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --region "$REGION" --group-id "$SG_ID" \
--protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --region "$REGION" --group-id "$SG_ID" \
--protocol tcp --port 80 --cidr <your-client-cidr>
aws ec2 run-instances --region "$REGION" \
--image-id "$AMI_ID" --instance-type m5.large \
--key-name "$KEY_NAME" --security-group-ids "$SG_ID" --subnet-id "$SUBNET_ID" \
--metadata-options "HttpTokens=required" \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=nchan-01}]'
Step 3: Connect to Your Instance
Connect over SSH as the default login user for the AMI variant you launched, using the EC2 key pair you selected at launch. The Nchan listing offers the following variant:
| AMI variant | SSH login user |
|---|---|
| Nchan on Ubuntu 24.04 | ubuntu |
ssh -i /path/to/your-key.pem ubuntu@<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
Step 5: Get This Instance's Credential
There is no default password. On first boot nchan-firstboot.service generated a credential unique to this instance, 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 instance gets its own):
NCHAN_PUBLISH_USER=publisher
NCHAN_PUBLISH_PASSWORD=8f2c...
NCHAN_URL=http://<instance-public-ip>/
NCHAN_PUBLISH_ENDPOINT=http://<instance-public-ip>/pub/<channel>
NCHAN_SUBSCRIBE_ENDPOINT=http://<instance-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: 0 sec. ago
active subscribers: 0
last message id: 1784463365: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 — SSE framing, carrying the same payload:
id: 1784463365: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: 3 sec. ago
active subscribers: 0
last message id: 1784463365: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 instance 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 |
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 instance itself:
curl -s http://127.0.0.1/nchan_stub_status | head -6
Expected output:
total published messages: 2
stored messages: 1
shared memory used: 20K
shared memory limit: 131072K
channels: 1
subscribers: 0
From any other address the same URL is refused:
IP=$(hostname -I | awk '{print $1}')
curl -s -o /dev/null -w 'stub status from %{remote_ip} -> HTTP %{http_code}\n' "http://$IP/nchan_stub_status"
Expected output:
stub status from 172.31.0.13 -> 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
Expected output:
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running)
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 Application Load Balancer with an ACM certificate in front of port 80, or terminate on the instance 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.example.com
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 instance handles a very large number of concurrent subscribers, because idle connections cost nginx very little. If you outgrow one instance, 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 instance 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), or Amazon ElastiCache for Valkey
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 AWS
Find Nchan Pub/Sub Message Server in the AWS Marketplace, or browse the full cloudimg catalogue at cloudimg.co.uk.
Need Help?
Contact cloudimg support at support@cloudimg.co.uk.