Workload · Running a MediaMTX media server

Run a MediaMTX server for your streams, cameras and viewers

A MediaMTX server sits in the middle of your stream: your encoder or camera sends it one feed, and it hands that feed to every viewer in whatever their player speaks, WebRTC in a browser, HLS on a phone, RTSP or SRT for other software, without re-encoding the picture. So it asks little of the processor and a lot of the network. Here is what it needs, the server that fits, an install from the official release with its checksum checked, and the one default to change before you go live: anyone may publish.

The short version
  1. One feed in, any protocol out, no re-encoding
  2. Viewers are limited by your port, not your CPU
  3. Out of the box, anyone can publish to any path
  4. Single executable, MIT licence, runs under systemd

What your MediaMTX server will do

Think of it as a switchboard for live video. Each stream gets a name, called a path, like /studio or /camera1. Something pushes video into the path: OBS over RTMP, a field encoder over SRT, a browser over WebRTC. Or MediaMTX goes and fetches it itself, for example from an IP camera's RTSP address set as the path's source. Then anyone allowed to read the path can watch it in the protocol their player prefers.

The project calls itself a media router, and that is the right picture: it moves packets from one protocol's wrapping into another's without touching the video inside. If you need a smaller copy of your stream, that is FFmpeg's job, run alongside it.

You want toProtocol and default port
Send from OBSRTMP, 1935 TCP
Send over a shaky uplinkSRT, 8890 UDP
Watch in a browser with the least delayWebRTC, 8889 TCP plus 8189 UDP
Watch on almost anythingHLS, 8888 TCP
Pull from or serve to cameras and NVR softwareRTSP, 8554 TCP

Before you go live: close the open door

The configuration that ships with MediaMTX trusts everyone. Its first user is any, from any address, allowed to publish, read and play back recordings on every path. On top of that, overridePublisher is on, which lets a second publisher push the first one off a path. Leave it that way on a public address and a stranger can replace your stream with theirs.

The setup below fixes that with a short config file of our own. Anything it does not mention keeps the default from the 1.21.3 release. Here is everything it changes: a user called encoder with a random password who may only publish; anonymous viewers who may only read; the stock rule that limits the API, metrics and pprof to the machine itself; your public IP for WebRTC; and recordings kept under /var/lib/mediamtx. Anonymous publishing and anonymous playback are gone.

Will one server carry your audience?

Usually, yes, and the question to ask is about your network port rather than your processor. The MediaMTX scalability page puts it plainly: because it does not re-encode, the limit is nearly always the bandwidth between the server and the people watching. Every viewer receives their own copy, so multiply viewers by bitrate and keep the total under 80 percent of the port.

  • At 1080p and 6 Mbps a viewer, about 133 people fit on a 1 Gbps port at the same time.
  • At 720p and 4 Mbps, about 200.
  • A 10 Gbps port carries ten times each.

MediaMTX's docs list no minimum processor or memory. The processor starts to matter only when you add FFmpeg to make extra qualities, since every rendition is an encode of its own. If you need a full ladder on many channels, look at a GPU transcoding server beside this one.

The server that fits

A dedicated streaming server gives MediaMTX a whole machine and a switch port that only you use. Nothing counts the traffic: no allowance, no overage line, the same bill in a busy month and a quiet one. You get root, so MediaMTX goes on exactly as below. The project's install page recommends its official container, bluenviron/mediamtx:1, for production; the release binary used here is the same program as one file under systemd, and either runs on this machine.

  • The streaming line runs a Xeon E3-1585L v5 with Iris Pro graphics, 32 GB and NVMe, on an included 1 Gbps port, in any of five rooms: Amsterdam, Bucharest, Miami, New York or San Francisco.
  • Ports go up to 10, 20, 40 and 100 Gbps on the lines that sell them.
  • A racked machine comes up about 30 minutes after your payment is approved. A build to order, which includes any port above 1 Gbps, takes 4 to 24 hours.
  • Need to see the screen when the network is down? Blades, rack servers, m510s and m710xs give you a remote console; the remaining cartridge models give you power control and a reinstall button instead.
  • Phone, live chat and tickets at any hour, on every plan.

From download to first viewer

  1. Install Ubuntu 22.04 or 24.04, then work as root (sudo -i).
  2. Download the release, check it against the published checksum, and give MediaMTX a system user and folders of its own:
# as root on Ubuntu 22.04 or 24.04, x86-64
V=v1.21.3; F=mediamtx_${V}_linux_amd64.tar.gz
U=https://github.com/bluenviron/mediamtx/releases/download/$V
cd /root && curl -fLO "$U/$F" && curl -fLO "$U/checksums.sha256" \
  && grep "$F" checksums.sha256 | sha256sum --check \
  && mkdir -p "mediamtx-$V" && tar -xzf "$F" -C "mediamtx-$V" \
  && install -m 755 "mediamtx-$V/mediamtx" /usr/local/bin/mediamtx \
  && { id mediamtx >/dev/null 2>&1 || useradd --system --no-create-home --shell /usr/sbin/nologin mediamtx; } \
  && install -d -m 700 -o mediamtx -g mediamtx /usr/local/etc/mediamtx /var/lib/mediamtx \
  && /usr/local/bin/mediamtx --version
  1. Put your IP in the next block and run it. It enables ufw, keeping SSH open, before MediaMTX ever listens, opens RTMP, HLS, WebRTC and SRT, writes the config at mode 600 with a fresh password and installs the systemd unit. MediaMTX starts only if --validate-conf passes on the new file, and the block leaves an existing config alone.
  2. Open 8554/tcp yourself only if cameras or RTSP players must reach the server.
IP=203.0.113.10   # your server's public IPv4
C=/usr/local/etc/mediamtx/mediamtx.yml
if [ "$IP" = "203.0.113.10" ]; then echo "STOP: put your IP in IP= first"
elif [ -e "$C" ]; then echo "STOP: $C already exists"
else
  sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw && ufw allow OpenSSH && ufw --force enable \
  && ufw allow proto tcp to any port 1935,8888,8889 && ufw allow proto udp to any port 8189,8890 \
  && K=$(openssl rand -hex 16) && [ -n "$K" ] \
  && install -m 600 -o mediamtx -g mediamtx /dev/null "$C" && cat > "$C" <<EOF
authInternalUsers:
  - {user: encoder, pass: $K, permissions: [{action: publish}]}
  - {user: any, permissions: [{action: read}]}
  - user: any
    ips: ["127.0.0.1", "::1"]
    permissions: [{action: api}, {action: metrics}, {action: pprof}]
webrtcAdditionalHosts: [$IP]
pathDefaults:
  recordPath: /var/lib/mediamtx/recordings/%path/%Y-%m-%d_%H-%M-%S-%f_%z
paths:
  all_others:
EOF
  tee /etc/systemd/system/mediamtx.service >/dev/null <<'EOF'
[Unit]
After=network-online.target
Wants=network-online.target
[Service]
User=mediamtx
WorkingDirectory=/usr/local/etc/mediamtx
ExecStart=/usr/local/bin/mediamtx
Restart=always
[Install]
WantedBy=multi-user.target
EOF
  /usr/local/bin/mediamtx --validate-conf="$C" && systemctl daemon-reload \
  && systemctl enable systemd-networkd-wait-online.service \
  && systemctl enable --now mediamtx && echo "live; encoder password is in $C"
fi

If --validate-conf prints an ERR line, nothing has started. Either fix the file and finish with /usr/local/bin/mediamtx --validate-conf=/usr/local/etc/mediamtx/mediamtx.yml && systemctl daemon-reload && systemctl enable systemd-networkd-wait-online.service && systemctl enable --now mediamtx, or delete the file and run the whole block again, which writes a fresh password. The unit is the one from the project's start-on-boot page with two lines of ours added: User=mediamtx, so it does not run as root, and Restart=always. In OBS, choose Custom as the service, enter rtmp://YOUR-IP/studio?user=encoder&pass=YOUR-PASSWORD as the server and keep the stream key blank. Your viewers open http://YOUR-IP:8888/studio, or :8889/studio for WebRTC. Prefer WebRTC from OBS itself? Choose WHIP as the service, use http://YOUR-IP:8889/studio/whip as the server and encoder:YOUR-PASSWORD as the Bearer Token.

RTMP sends that password in the clear. The authentication page recommends encryption or a VPN so credentials cannot be read in transit. SRT can be encrypted by setting srtPublishPassphrase on the path and the same passphrase in your encoder.

When a browser will not play it

  • WebRTC connects but stays black. Check the codecs first. MediaMTX's WebRTC table has no AAC audio, and browsers refuse H.264 with B-frames. Its OBS guide uses a second output with Opus audio and bf=0 for streams meant for WebRTC; that output speaks RTSP, so you would open 8554/tcp and send it to a path of its own, rtsp://encoder:YOUR-PASSWORD@YOUR-IP:8554/studio-rtc (viewers open :8889/studio-rtc), or use it with Start Recording instead of RTMP streaming, as the guide does. Publishing over WHIP is the simpler fix. HLS plays AAC fine.
  • WebRTC never connects. The browser must reach UDP 8189 at the address in webrtcAdditionalHosts. If UDP is blocked on the viewer's side, the docs suggest a TCP listener next (webrtcLocalTCPAddress: :8189, plus ufw allow 8189/tcp), then a STUN server, and a TURN relay as the last resort.
  • HLS misbehaves on iPhones. The default HLS variant is Low-Latency, which the config file says needs HTTPS on Apple devices. Set hlsEncryption with a certificate and key for your hostname, or change hlsVariant to mpegts.
  • Your website is https. Set webrtcEncryption and a certificate for WebRTC too, then embed the player page from https:// in an iframe.

Keeping it healthy

You can edit mediamtx.yml while the server runs; changes apply without disconnecting viewers wherever that is possible. To update, repeat the download and checksum steps with the new version, install the binary over the old one and systemctl restart mediamtx. The binary's own --upgrade flag also replaces itself with the latest release.

Recording stays off until you add record: yes under pathDefaults, and then it keeps segments for one day by default (recordDeleteAfter: 1d). Raise that, or set 0s, and keep an eye on the disk.

Back up the config folder every night with a cron file: `install -d -m 700 /root/mediamtx-backups && printf '%s\n' '40 3 * * * root umask 077; tar -czf /root/mediamtx-backups/mediamtx-$(date +\%F-\%H\%M\%S).tar.gz -C / usr/local/etc/mediamtx etc/systemd/system/mediamtx.service && find /root/mediamtx-backups -name "mediamtx-*.tar.gz" -mtime +14 -delete' > /etc/cron.d/mediamtx-backup. The prune runs only once a fresh archive has been written. Copy the folder, and /var/lib/mediamtx/recordings if you keep recordings, to another machine with rsync -a, never with --delete`.

To restore, name the archive. On a fresh machine, run the two setup blocks first so the unit exists; the restore then sets their new config aside. This block stops on an unreadable archive or a leftover .previous folder, moves your current config aside rather than deleting it, and restarts only after the restored file validates. The unit file is in the archive for reference and is left as it is. On a new machine, put its IP into webrtcAdditionalHosts after the restore; the encoder password is the archive's, not the one the setup block generated.

# as root; name the archive you want back
A=/root/mediamtx-backups/mediamtx-YYYY-MM-DD-HHMMSS.tar.gz
E=/usr/local/etc/mediamtx
if ! tar -tzf "$A" usr/local/etc/mediamtx/mediamtx.yml >/dev/null 2>&1; then
  echo "STOP: $A is unreadable or holds no config"
elif [ -e "$E.previous" ]; then echo "STOP: $E.previous exists; move it away first"
else
  systemctl stop mediamtx && mv "$E" "$E.previous" \
  && tar -xzf "$A" -C / usr/local/etc/mediamtx \
  && /usr/local/bin/mediamtx --validate-conf="$E/mediamtx.yml" \
  && systemctl start mediamtx && echo "restored; your old folder is $E.previous"
fi

Changed your mind? `systemctl stop mediamtx; [ -d /usr/local/etc/mediamtx.previous ] && [ ! -e /usr/local/etc/mediamtx.failed ] && { [ ! -e /usr/local/etc/mediamtx ] || mv /usr/local/etc/mediamtx /usr/local/etc/mediamtx.failed; } && mv /usr/local/etc/mediamtx.previous /usr/local/etc/mediamtx && systemctl start mediamtx puts the old folder back. When all is well, delete mediamtx.previous` so the next restore can run.

When you would be better off with something else

If your viewers expect chat beside the video, Owncast is closer to the finished thing. If you only need OBS in and HLS out, nginx with the RTMP module is a smaller moving part. And if most of your work is tuning a contribution link from a venue, read the SRT streaming server page first. MediaMTX earns its place when several protocols have to meet in one box.

Questions people ask

Can MediaMTX make lower-quality versions of my stream?
Not on its own. It converts between protocols but leaves the video as it arrived. For a 720p copy, install FFmpeg (apt install ffmpeg) and run it against the server: read the original path and publish the result to a new one, such as rtsp://encoder:YOUR-PASSWORD@localhost:8554/studio720, which is the pattern the project's re-encoding page shows. The password is needed because only the encoder user may publish. Budget processor time, or a GPU, for every extra rendition.
How do I stop other people publishing to my MediaMTX server?
Replace the default any user that has publish rights. Give publishing to a named user with a password, keep reading open if your stream is public, and pass the credentials in the address: ?user=...&pass=... for RTMP, or publish:path:user:pass in the SRT stream ID. The page's config does exactly that.
Will MediaMTX run on Windows Server?
Yes. The project publishes a Windows build, and its start-on-boot page installs it as a service with WinSW. Windows Server carries its own per-core licence fee on our machines, so Ubuntu is the cheaper home if you have no other reason to run Windows.
Which ports does a MediaMTX server need?
Only the ones your encoders and viewers use. A typical setup opens 1935 TCP for RTMP, 8890 UDP for SRT, 8888 TCP for HLS, and 8889 TCP with 8189 UDP for WebRTC. The admin listeners, pprof on 9999, metrics on 9998, the control API on 9997 and recording playback on 9996, ship disabled; should you enable them, keep them off the internet.
Can I use MediaMTX for a paid service?
This is our general reading of the licences and not legal advice. MediaMTX itself is MIT; the project's license page puts its Go libraries under Apache 2.0, BSD 3-Clause or MIT, and hls.js under Apache 2.0. All of these allow commercial use. Passing the binary on means passing on each copyright notice and licence text, plus any NOTICE file, but you never have to publish your own source.

Give your streams a server of their own

A whole machine, an unmetered port nobody else shares, root to install MediaMTX your way, and five rooms to put it in.

Get a server for your streams

Read next