Broadcast Box on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of Broadcast Box on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Broadcast Box is an open source WebRTC live streaming server. It implements WHIP for ingest and WHEP for playback, the two standards that carry video over the same real time transport a video call uses, so the delay between something happening in front of the camera and a viewer seeing it is a fraction of a second rather than the ten to thirty seconds a traditional segmented streaming pipeline imposes.
You broadcast from OBS Studio or any encoder that speaks WHIP, or directly from a browser tab, and viewers watch in any modern browser with no plugin, no app and no third party streaming service in the video path.
The image is compiled from the pinned upstream release at build time and the compilers and source tree are then removed, so what ships is a single static Go binary at /usr/local/bin/broadcast-box plus the built web interface. It runs under systemd as the non root broadcastbox user.
Security by design — publishing is closed by default. This is a deliberate departure from the upstream default and the most important thing to understand about this image. Broadcast Box normally runs with STREAM_PROFILE_POLICY=ANYONE_WITH_RESERVED, under which any anonymous caller may publish to any stream key that has not been reserved. On a virtual machine with a public address that is an open media relay: a stranger who finds your instance can push video through your bandwidth. This image ships STREAM_PROFILE_POLICY=RESERVED instead:
-
Every publish requires a bearer token belonging to a reserved stream profile. Exactly one such profile exists and it is created on this VM's first boot. An anonymous or wrong credentialled publish attempt is refused.
-
The admin portal requires a separate token, also generated on first boot. The portal and the whole
/api/admin/*surface return HTTP 401 without it. -
No credential of any kind is baked into the image. Two machines launched from the same image get different tokens.
Playback is open to anyone holding the stream key, which is what makes a player link shareable. The stream key is random per VM so it is not guessable, but treat it as a capability: anyone you give it to can watch. Mint additional reserved profiles from the admin portal if you need to revoke access for one audience without disturbing another.
What is included:
-
Broadcast Box (compiled from the pinned upstream release), run under systemd as the unprivileged
broadcastboxuser (broadcast-box.service) -
The web interface on port 80 — stream discovery, browser publishing, a player with cinema mode, a statistics page and an admin portal
-
All WebRTC media muxed onto a single UDP port (3478), so you open one UDP port rather than an ephemeral range
-
A per VM admin token and a per VM reserved stream profile generated on first boot and written to a root only credentials file
-
A shipped round trip self test that proves the auth model end to end, including that an anonymous publish is refused
-
Ubuntu 24.04 LTS, fully patched, with unattended security upgrades enabled
Prerequisites
-
Active Azure subscription, SSH public key, VNet + subnet in the target region
-
Subscription to the Broadcast Box listing on Azure Marketplace
-
Network Security Group rules allowing TCP 22 (admin), TCP 80 (web interface and WHIP/WHEP signalling) and UDP 3478 (all WebRTC media)
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample for a handful of concurrent viewers. Broadcast Box forwards media without transcoding, so its cost is bandwidth rather than CPU; increase the size for large viewer counts.
Step 1: Deploy from the Azure Portal
Search Broadcast Box in Marketplace, select the cloudimg publisher, and click Create. Configure the Network Security Group to allow TCP 80 and UDP 3478 from the networks your broadcasters and viewers will connect from, and TCP 22 for administration. For internet exposure, front port 80 with TLS (see Step 9).
Step 2: Deploy from the Azure CLI
RG="broadcast-box-prod"; LOCATION="eastus"; VM_NAME="broadcastbox1"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/broadcast-box-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az vm create \
--resource-group "$RG" --name "$VM_NAME" \
--image "$GALLERY_IMAGE_ID" \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values "$SSH_KEY" \
--public-ip-sku Standard \
--os-disk-delete-option Delete --nic-delete-option Delete
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 80 --priority 1001
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 3478 --protocol Udp --priority 1002
Both rules matter. Opening TCP 80 alone produces the classic WebRTC failure: the page loads, the player appears to connect, and no video ever arrives, because signalling succeeds over HTTP while the media plane is silently blocked. If a stream never starts, check the UDP rule first.
Step 3: First boot
On the first boot the image generates a unique admin token, stream key and publish bearer token for this VM, writes the single reserved stream profile, records the machine's public address so WebRTC advertises a reachable path, and arms the service. Nothing is shared between VMs.
Read your credentials over SSH:
sudo cat /root/broadcast-box-credentials.txt
# Broadcast Box — Per-VM Instance Info
# Generated: Sun Jul 19 13:34:42 UTC 2026
#
# There are NO default credentials. Every value below was generated uniquely on
# THIS VM's first boot and was never baked into the image. Keep this file secret
# (root only).
#
broadcastbox.url=http://<your-vm-ip>/
broadcastbox.admin_url=http://<your-vm-ip>/admin
broadcastbox.admin_token=****
broadcastbox.stream_key=cimg-95680abd4df2fa38
broadcastbox.stream_token=****
broadcastbox.whip_endpoint=http://<your-vm-ip>/api/whip
broadcastbox.player_url=http://<your-vm-ip>/cimg-95680abd4df2fa38
The file also records the OBS settings and the network rules you need. Keep it secret: the stream token is your publishing credential.
Step 4: Confirm the server is running
systemctl is-active broadcast-box
ss -tln | grep -E ':80 |:22 '
ss -uln | grep ':3478 '
active
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:*
LISTEN 0 4096 [::]:22 [::]:*
LISTEN 0 4096 *:80 *:*
UNCONN 0 0 10.0.0.8:3478 0.0.0.0:*
The listener set is deliberately small: the Broadcast Box HTTP server on port 80 and sshd on port 22 are the only externally bound TCP services, and UDP 3478 carries all WebRTC media for both publishing and playback. No database is installed, because Broadcast Box is stateless — streams are live sessions held in memory and nothing is recorded to disk.
The server binds port 80 as a non root user through a single Linux capability rather than running as root:
systemctl show -p User -p AmbientCapabilities -p ProtectSystem -p NoNewPrivileges --value broadcast-box.service
cap_net_bind_service
broadcastbox
strict
yes

Step 5: Run the round trip self test
The image ships a self test that proves the access control actually works, rather than merely that a port is open. A liveness check would pass on an image that lets the whole internet publish. The self test authenticates with the per VM admin token, confirms the per VM stream token is accepted for publishing, and then runs the negative controls that matter.
sudo bash /usr/local/lib/cloudimg/broadcast-box-roundtrip.sh
ROUNDTRIP OK: the frontend (200) and public status API (200) serve; the per-VM admin
token authenticated against /api/admin/login (200) while a missing header and a wrong
token were both rejected (401); an ANONYMOUS WHIP publish was refused, an EMPTY bearer
was refused 401, and a WRONG bearer was refused 401 by STREAM_PROFILE_POLICY=RESERVED,
proving the ingest endpoint is not an open relay; and the per-VM reserved stream token
passed WHIP authorisation (HTTP 400, not 401), proving the customer's own publish
credential works. Stream key cimg-95680abd4df2fa38 is the reserved playback path.

Step 6: Verify the auth model yourself
You do not have to take the self test's word for it. These five requests are the whole security model, and you can run them directly.
echo "admin login, correct token: $(curl -s -o /dev/null -w '%{http_code}' -X POST -H 'Authorization: Bearer <BB_ADMIN_TOKEN>' http://127.0.0.1/api/admin/login)"
echo "admin login, wrong token: $(curl -s -o /dev/null -w '%{http_code}' -X POST -H 'Authorization: Bearer wrong' http://127.0.0.1/api/admin/login)"
echo "publish, anonymous: $(curl -s -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/sdp' --data-binary 'v=0' http://127.0.0.1/api/whip)"
echo "publish, wrong bearer: $(curl -s -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/sdp' -H 'Authorization: Bearer not-a-reserved-token' --data-binary 'v=0' http://127.0.0.1/api/whip)"
echo "publish, your stream token: $(curl -s -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/sdp' -H 'Authorization: Bearer <BB_STREAM_TOKEN>' --data-binary 'v=0' http://127.0.0.1/api/whip)"
admin login, correct token: 200
admin login, wrong token: 401
publish, anonymous: 400
publish, wrong bearer: 401
publish, your stream token: 400
Reading those results:
-
The admin portal is closed. The correct token authenticates; anything else is
401. -
Publishing is closed. An anonymous attempt is refused (
400, Authorization was not set) and a wrong bearer token is refused401by the reserved stream policy. On a stock Broadcast Box deployment that wrong bearer would have been accepted and started a stream. -
Your own token gets through authorisation. It returns
400rather than401becausev=0is not a negotiable WebRTC offer, so the request fails later, during media negotiation — exactly as it should. The distinction between400and401here is the whole point:401means rejected at the door,400means admitted and then given nonsense. A real encoder sends a real offer and streams.

Step 7: Inspect your reserved stream profile
The admin API lists the reserved profiles the server has loaded. This is the authoritative check that your publish credential is live.
curl -s -H 'Authorization: Bearer <BB_ADMIN_TOKEN>' http://127.0.0.1/api/admin/profiles
echo
curl -s -o /dev/null -w 'admin status, with token: %{http_code}\n' -H 'Authorization: Bearer <BB_ADMIN_TOKEN>' http://127.0.0.1/api/admin/status
curl -s -o /dev/null -w 'admin status, no token: %{http_code}\n' http://127.0.0.1/api/admin/status
[{"streamKey":"cimg-95680abd4df2fa38","token":"****","isPublic":true,"motd":"cloudimg Broadcast Box"}]
admin status, with token: 200
admin status, no token: 401

Step 8: Broadcast with OBS Studio
OBS Studio 30 and later speaks WHIP natively. In Settings → Stream:
- Service:
WHIP - Server:
http://<your-vm-ip>/api/whip - Bearer Token: the
broadcastbox.stream_tokenvalue from your credentials file
Click OK, then Start Streaming. Because WebRTC is latency sensitive rather than bitrate hungry, a moderate bitrate and a short keyframe interval give the best experience. Viewers then watch at:
http://<your-vm-ip>/<your-stream-key>
Any WHIP capable encoder works the same way — ffmpeg 8 and later, GStreamer with the appropriate plugins, or a browser tab via the publish page. You can also publish straight from the browser by opening http://<your-vm-ip>/publish/<your-stream-key>, which captures your screen or webcam without any software installed.
Step 9: Use the web interface
The home page lists the streams currently live on this instance, and doubles as the entry point for viewers.

Opening a stream key as a path gives the player. With no broadcaster connected it says so plainly; once an encoder connects, video appears in well under a second.

Switching the toggle to I want to stream turns the same panel into the publishing form: enter your stream key and start broadcasting directly from a browser tab, capturing a screen, a window or a webcam with nothing installed.

The statistics page reports stream uptime and subscriber counts, and the admin portal at /admin asks for the admin token before showing active publishers and subscribers or letting you manage reserved stream profiles.

Step 10: Front the server with TLS for internet exposure
Browsers restrict camera and screen capture to secure contexts, so publishing from a browser tab requires HTTPS on anything other than localhost. Playback works over plain HTTP, but you should terminate TLS for any internet facing deployment regardless.
Point a DNS record at the VM, then place a TLS terminating proxy in front of port 80. Broadcast Box also accepts SSL_CERT and SSL_KEY directly if you prefer it to terminate TLS itself; set them in /etc/broadcast-box/broadcast-box.env and restart the service. Whichever route you take, UDP 3478 must remain open — media never travels over the TLS connection.
Step 11: Security recommendations
-
Keep the stream token secret. It is the publishing credential. Anyone who holds it can broadcast on your instance.
-
Treat the stream key as a viewing capability. Playback is deliberately open to whoever holds the key so that links are shareable. If different audiences must not share access, create a separate reserved profile per audience from the admin portal and revoke individually.
-
Scope your Network Security Group. Restrict TCP 80 and UDP 3478 to the networks that genuinely need them rather than the whole internet, especially while testing.
-
Rotate tokens from the admin portal if a credential is ever exposed. The credentials file records the values generated at first boot; it is not updated by later rotations.
-
Leave unattended security upgrades enabled. The image ships fully patched with automatic security updates armed.
-
The service runs as the unprivileged
broadcastboxuser with a strict read only filesystem, no new privileges, and only the single capability it needs to bind port 80.

Step 12: Support and Licensing
Broadcast Box is distributed under the MIT license. This is a repackaged open source software product with additional charges for cloudimg support services. cloudimg is not affiliated with or endorsed by Glimesh or the Broadcast Box project.
The vendored license text ships on the image at /usr/local/share/doc/broadcast-box/LICENSE.
For assistance with this image, contact cloudimg support, available 24/7, at support@cloudimg.co.uk.