Media & Entertainment AWS

MistServer 3.13 on AWS User Guide

| Product: MistServer 3.13 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the MistServer 3.13 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg media servers listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

MistServer is an open source streaming media server. An encoder such as OBS Studio or ffmpeg publishes a live stream to it over RTMP, and MistServer repackages that stream on the fly for every kind of viewer: HLS for browsers and phones, CMAF (MPEG-DASH and fragmented MP4), progressive MPEG-TS, MP4 and FLV, plus a built in web player. It can also pull streams from other servers and record or re-push them.

What cloudimg adds:

  • Built from the upstream 3.13 source. MistServer is compiled by cloudimg from the checksum verified MistServer 3.13 release tag (commit ddd9bec7). The upstream code is unmodified; the one change is the dependency pin, which is raised from Mbed TLS 3.6.6 to the current Mbed TLS 3.6.7 security release. The exact diff ships on the image.
  • A per-instance admin login, created before any port opens. First boot generates the administrator password and the RTMP publishing passphrase for your instance and writes them to /root/mistserver-credentials.txt. MistServer and nginx refuse to start until that has happened, so there is never a window in which the first visitor could claim the server. MistServer allows only that one account.
  • HTTPS only management. The management interface and API are served by nginx on port 443 with a certificate generated on your instance's first boot, behind the admin login. Port 80 redirects to HTTPS and answers /healthz for load balancers. The MistServer controller itself listens only on 127.0.0.1:4242.
  • Publishing protected by a passphrase. The ready made live stream accepts an RTMP push only when the publisher supplies your instance's passphrase.
  • A sandboxed service that cannot reach inside your network. MistServer runs as its own unprivileged account in a strict systemd sandbox. A firewall table loaded at every boot stops that account from opening connections to the EC2 instance metadata service, to private and reserved address ranges, and to your VPC's own IPv4 and IPv6 ranges, so a stream source URL cannot be used to reach your internal services. Public sources work as usual.
  • No SRT or RIST, no ffmpeg. SRT and RIST support are not compiled in. The image ships no ffmpeg; MistServer packages streams itself.
  • Fully patched Ubuntu 24.04 LTS at build time, IMDSv2 required, with unattended security upgrades switched on.

What is on the image: MistServer 3.13 in /opt/mistserver (compiled by cloudimg, with its bundled Mbed TLS 3.6.7, libsrtp 2.6.0 and usrsctp 0.9.5.0 libraries in /opt/mistserver/lib), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet.
  • A security group that allows:
  • TCP 22 (SSH) from your own networks only
  • TCP 443 (management interface and API over HTTPS) from your administrators' networks
  • TCP 80 (redirect to HTTPS and /healthz) from the same sources as 443
  • TCP 1935 (RTMP publishing) from your encoders
  • TCP 8080 (playback: HLS, CMAF, MPEG-TS, MP4, FLV and the web player) from your viewers or your CDN, usually anywhere

Streaming is mostly network bound: MistServer repackages streams without transcoding, so an m5.large serves many concurrent viewers of a few streams. Put a CDN such as Amazon CloudFront in front of port 8080 for large audiences.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick MistServer 3.13 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=mistserver}]'

Step 2: Connect to your instance

Version SSH user
MistServer 3.13 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the certificate, the admin password and the publishing passphrase, then starts MistServer and nginx:

systemctl is-active mistserver-egress-guard mistserver nginx
sudo test -f /var/lib/cloudimg/mistserver-firstboot.done && echo FIRSTBOOT_DONE
/opt/mistserver/bin/MistController -v 2>&1 | head -1

All three units are active, the first boot marker is present and MistServer reports its version:

active
active
active
FIRSTBOOT_DONE
Version: 3.13, release Generic_x86_64

nginx serves the management interface on 80 and 443; MistServer publishes RTMP on 1935 and playback on 8080, and its controller listens on loopback only:

sudo ss -lntpH | awk '{split($6, a, "\""); print $4, a[2]}' | sort -u
*:1935 MistOutRTMP
*:8080 MistOutHTTP
0.0.0.0:22 sshd
0.0.0.0:443 nginx
0.0.0.0:80 nginx
127.0.0.1:4242 MistController
127.0.0.53%lo:53 systemd-resolve
127.0.0.54:53 systemd-resolve
[::]:22 sshd
[::]:443 nginx
[::]:80 nginx

Step 4: Get your login and publishing details

sudo grep -E '^MIST_(HOST|URL|ADMIN_USER|HLS_URL|TS_URL|PLAYER_URL)=' /root/mistserver-credentials.txt
sudo stat -c '%a %U %n' /root/mistserver-credentials.txt
MIST_HOST=ec2-203-0-113-10.compute-1.amazonaws.com
MIST_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/
MIST_ADMIN_USER=admin
MIST_HLS_URL=http://ec2-203-0-113-10.compute-1.amazonaws.com:8080/hls/live/index.m3u8
MIST_TS_URL=http://ec2-203-0-113-10.compute-1.amazonaws.com:8080/live.ts
MIST_PLAYER_URL=http://ec2-203-0-113-10.compute-1.amazonaws.com:8080/live.html
600 root /root/mistserver-credentials.txt

MIST_HOST is the instance's public DNS name from EC2 instance metadata; the certificate also names the public and private IP addresses. To use your own DNS name, see Step 9.

The full file, which also holds MIST_ADMIN_PASSWORD and MIST_PUSH_PASSPHRASE, is readable by root only. Show it when you need the values:

sudo cat /root/mistserver-credentials.txt

Keep the password and the passphrase secret; anyone with the passphrase can publish to your live stream.

Step 5: Open the management interface

Browse to the MIST_URL from Step 4 (https://<public-ip>/) and accept the self signed certificate warning (Step 9 shows how to use your own name and certificate). Your browser asks for a user name and password: enter admin and your MIST_ADMIN_PASSWORD. The MistServer interface then shows its own login; enter the same user name and password.

The MistServer management interface login page served over HTTPS, with the host filled in and fields for the user name and password

The Overview page shows the MistServer version, the configured and active streams, the current connections and the enabled protocols:

The MistServer Overview page showing version 3.13, one configured stream, the enabled protocols RTMP, HTTP, HTTPTS, HLS, CMAF, MP4 and FLV, and no recent problems

Step 6: Publish a stream and play it back

The image comes with a stream named live that accepts an RTMP push carrying your passphrase. In OBS Studio, open Settings > Stream, choose Custom, and enter:

  • Server: rtmp://<public-ip>:1935/<passphrase> (your MIST_PUSH_PASSPHRASE)
  • Stream Key: live

With ffmpeg on your own computer, the same publish is:

ffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast -c:a aac -f flv "rtmp://<public-ip>:1935/<passphrase>/live"

A publish with a missing or wrong passphrase is refused. While you are publishing, viewers can play the stream on port 8080:

  • Web player page: http://<public-ip>:8080/live.html
  • HLS (browsers, iOS, Android, smart TVs): http://<public-ip>:8080/hls/live/index.m3u8
  • CMAF / MPEG-DASH: http://<public-ip>:8080/cmaf/live/index.mpd
  • MPEG-TS: http://<public-ip>:8080/live.ts
  • MP4 / FLV: http://<public-ip>:8080/live.mp4, http://<public-ip>:8080/live.flv

The Streams page lists live with its state, viewers and inputs:

The MistServer Streams page listing the ready made live stream, inactive until an encoder publishes to it

The image includes a self test that does all of this on the instance itself: it checks that anonymous and wrong password requests are refused and that a second account cannot be created, logs in, publishes a short silent test stream to live over RTMP with a wrong passphrase (refused) and then with yours (accepted), and plays it back as MPEG-TS and HLS:

sudo mist-selftest

The last line sums up every check:

OK MistServer self-test passed: HTTPS front 401 anonymous / wrong password, API CHALL without login, second account refused, admin login OK, wrong-passphrase RTMP publish refused, passphrase publish accepted, MPEG-TS (65536 bytes) + HLS pulled back from :8080

The test uses a temporary stream that it removes when it finishes; your live stream configuration is left as it was. Run it when no encoder is publishing to live.

Step 7: Protocols and the API

The Protocols page lists the enabled inputs and outputs: RTMP on 1935 and the HTTP output on 8080, which carries HLS, CMAF, MPEG-TS, MP4, FLV and the web player.

The MistServer Protocols page listing the seven active protocols: RTMP on port 1935 and the HTTP based outputs on port 8080

To script MistServer from the instance, use the bundled mist-api helper. It logs in over the local HTTPS front with your admin credentials and prints MistServer's JSON reply. The full reply includes the stream configuration, and with it your publishing passphrase, so this example prints only the fields it needs: whether the login was accepted, the streams being published right now and the enabled protocols:

sudo mist-api '{"active_streams":true}' | python3 -c 'import json, sys; d = json.load(sys.stdin); print("api:", d["authorize"]["status"]); print("active_streams:", d.get("active_streams")); print("protocols:", ", ".join(sorted(p["connector"] for p in d["config"]["protocols"])))'

With nothing publishing, the output is:

api: OK
active_streams: None
protocols: CMAF, FLV, HLS, HTTP, HTTPTS, MP4, RTMP

While an encoder publishes to live, active_streams lists ['live']. The API commands are described in the MistServer documentation.

WebRTC and RTSP are not enabled on this image, and the listing's security group opens no UDP ports. To add one, enable the protocol on the Protocols page (WebRTC needs a UDP port range and your instance's public IP set as its external address; RTSP uses TCP 554 by default), then open the same ports in your security group. Publishing over SRT and RIST is not available: they are not compiled into this build.

Step 8: Pull a stream from another server

MistServer can use a remote stream as a source: on the Streams page create a stream and set its source to, for example, rtmp://origin.example.com/app/stream or https://origin.example.com/live/index.m3u8. MistServer connects to the source when the first viewer arrives.

Two things to know:

  • Sources must be public. The firewall table described in the Overview refuses connections from MistServer to the instance metadata service, to private and reserved address ranges and to your VPC, so a source inside your VPC will not connect. If you need to pull from a private address, see Maintenance.
  • Outbound TLS is not verified. When MistServer pulls from an https:// or rtmps:// source it does not verify the remote server's certificate (MistServer sets Mbed TLS to VERIFY_NONE for outbound connections). The connection is encrypted but not authenticated, so pull only from sources you trust, over networks you trust. Inbound administration is not affected: it goes only through the nginx HTTPS front, with its own certificate.

Step 9: Use your own DNS name and certificate

Point a DNS record at the instance (an Elastic IP keeps the address stable), then tell the instance its name. This regenerates the self signed certificate for that name, updates the credentials file and reloads nginx:

sudo mistserver-set-hostname stream.your-domain.example

To use a certificate from a public CA instead, replace /etc/ssl/mistserver/mistserver.crt (full chain) and /etc/ssl/mistserver/mistserver.key (mode 0600) and run sudo systemctl reload nginx. Playback on port 8080 is plain HTTP; for HTTPS playback, put Amazon CloudFront or an Application Load Balancer with an AWS Certificate Manager certificate in front of port 8080.

Step 10: Change the password and passphrase

To replace the admin password and the publishing passphrase (for example after a staff change), run the rotation helper. It writes new values to MistServer, nginx and the credentials file; update your encoders with the new passphrase:

sudo mistserver-rotate-credentials

Step 11: Security checks

These checks show the protections described in the Overview on your own instance. First, the management front: an anonymous request gets the login challenge, plain HTTP redirects to HTTPS and the MistServer controller cannot be reached on the instance's network address:

H=$(sudo sed -n 's/^MIST_HOST=//p' /root/mistserver-credentials.txt)
curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w 'https://<host>/ without a login -> HTTP %{http_code}\n' "https://$H/"
curl -s --resolve "$H:80:127.0.0.1" -o /dev/null -w 'http://<host>/ -> HTTP %{http_code}\n' "http://$H/"
curl -s -m 5 -o /dev/null -w 'MistServer controller on the private address -> %{http_code}\n' "http://$(hostname -I | awk '{print $1}'):4242/" || true
https://<host>/ without a login -> HTTP 401
http://<host>/ -> HTTP 301
MistServer controller on the private address -> 000

000 means the connection was refused. Next, the egress sandbox: the mistserver account cannot reach the instance metadata service or the instance's own private address, and the firewall table and the systemd address filter are in place:

sudo -u mistserver curl -s -m 5 -o /dev/null -w 'uid mistserver -> instance metadata: %{http_code}\n' http://169.254.169.254/latest/meta-data/ || true
sudo -u mistserver curl -s -m 5 -o /dev/null -w "uid mistserver -> this instance's private address: %{http_code}\n" "http://$(hostname -I | awk '{print $1}')/healthz" || true
sudo nft list table inet cloudimg_mist_egress | grep -oE 'comment "cloudimg-[a-z0-9-]+"' | sort -u
systemctl show mistserver nginx -p IPAddressDeny --value
uid mistserver -> instance metadata: 000
uid mistserver -> this instance's private address: 000
comment "cloudimg-api-guard"
comment "cloudimg-api-guard-udp"
comment "cloudimg-api-guard6"
comment "cloudimg-api-guard6-udp"
comment "cloudimg-metadata"
comment "cloudimg-metadata6"
comment "cloudimg-multicast"
comment "cloudimg-multicast6"
comment "cloudimg-private"
comment "cloudimg-private6"
comment "cloudimg-vpc"
comment "cloudimg-vpc6"
168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32

The api-guard rules let only root, nginx and MistServer's own processes reach the controller port on loopback, so other local accounts cannot use the API; the vpc rules hold your VPC's IPv4 and IPv6 ranges, read from instance metadata at every boot. Finally, the build: the Mbed TLS library MistServer loads is 3.6.7, no SRT or RIST binaries are present, MistServer runs as its own account in a strict sandbox, and Ubuntu's shared snakeoil certificate service is masked:

python3 -c 'import ctypes; b = ctypes.create_string_buffer(32); ctypes.CDLL("/opt/mistserver/lib/libmbedcrypto.so.16").mbedtls_version_get_string_full(b); print(b.value.decode())'
ls /opt/mistserver/bin | grep -ciE 'srt|rist' || true
systemctl show mistserver -p User -p ProtectSystem -p NoNewPrivileges --value
systemctl is-enabled ssl-cert.service || true
Mbed TLS 3.6.7
0
mistserver
strict
yes
masked

Maintenance

  • Configuration: MistServer keeps its configuration in /var/lib/mistserver/mistserver.conf and saves changes you make in the interface there. Keep the controller on 127.0.0.1:4242: nginx publishes it, and a guard refuses to start MistServer if the controller is moved to a public address, a second account appears or the firewall table is missing.
  • Logs: journalctl -u mistserver for MistServer, /var/log/nginx/ for the management front.
  • Health checks: GET /healthz on port 80 or 443 returns ok without a login.
  • Private sources: the egress rules are in /usr/local/sbin/mistserver-egress-apply and are loaded by mistserver-egress-guard.service at every boot. Allowing a private source means editing that script; keep the metadata rules.
  • Security updates: unattended security upgrades are on and cover nginx and every system library MistServer uses. Reboot when a new kernel is installed. MistServer and its bundled Mbed TLS, libsrtp and usrsctp libraries are updated by a new version of this image.

Licences and source code

MistServer is released into the public domain under the Unlicense. It is built from the upstream 3.13 source; the only change is the Mbed TLS dependency pin, and that diff is in /usr/share/doc/mistserver-cloudimg/mbedtls-pin.diff. The bundled Mbed TLS 3.6.7 is licensed under the Apache License 2.0, libsrtp 2.6.0 and usrsctp 0.9.5.0 under the BSD 3-Clause licence, and the compiled in quirc library under the ISC licence. Every licence is in /usr/share/doc/mistserver-cloudimg/, with build details in README.cloudimg.

Support

cloudimg provides 24/7/365 expert technical support for this image. Contact support@cloudimg.co.uk. This image is provided by cloudimg and is not affiliated with or endorsed by DDVTech or the MistServer project.