ErsatzTV on Ubuntu 24.04 on Azure User Guide
Overview
ErsatzTV is an open source personal IPTV server. It turns your own local media, such as movies, shows and music videos, into live, always on television channels that play on a schedule you define, then streams those channels over HLS and MPEG-TS. It emulates an HDHomeRun network tuner and publishes an M3U playlist and an XMLTV electronic programme guide, so Plex, Jellyfin, Emby and any other IPTV client can add it as a live TV source and browse a proper channel guide. A full web interface lets you connect media libraries, build channels, arrange schedules and tune the ffmpeg transcoding, and a REST API is available for automation.
This cloudimg image installs the official ErsatzTV 26.6.0 self-contained linux-x64 build (recorded in /opt/ersatztv/VERSION) and runs it under systemd as the unprivileged ersatztv system user. Media transcoding uses jellyfin-ffmpeg 7.x, the modern ffmpeg build ErsatzTV recommends, installed from an apt repository so it keeps receiving security updates. ErsatzTV listens only on the local machine, and an nginx reverse proxy fronts it on port 80, which is the only public entry point. A unique administrator password is generated on the first boot of every VM and written to a root only file, so no shared credential ships in the image. Backed by 24/7 cloudimg support.
Secure by default. ErsatzTV has no login of its own on its management interface, which would leave a bare deployment open to anyone who can reach it, including full control of your channels and libraries. This image does not ship in that state. ErsatzTV is bound to the local machine by a host firewall (nftables) and placed behind an nginx reverse proxy, and that public entry point exposes the administration interface only after a unique administrator password is generated on each virtual machine's first boot. The password is hashed for the reverse proxy, and the plain value is placed in a root only file for the administrator to read, so no two deployments share a credential and there is never a window in which an unauthenticated interface is exposed to the network. The endpoints your IPTV players need, the tuner, the playlist and the guide, stay open without a password so Plex, Jellyfin and Emby keep working while the admin interface stays protected.
What is included:
- ErsatzTV 26.6.0 installed from the official self-contained release, verified against the checksum published by the project before it is unpacked
- jellyfin-ffmpeg 7.x for transcoding, exposed on
/usr/local/binso ErsatzTV auto-detects it - A dedicated
ersatztvsystemd service, hardened withNoNewPrivileges,PrivateTmp,ProtectSystemand a writable-paths allowlist, plus automatic restarts - An nginx reverse proxy on port 80: the admin UI behind per-VM HTTP Basic Auth, the IPTV client surface open for your players, and a static unauthenticated
/healthz - A host firewall (nftables) that keeps ErsatzTV's own ports reachable only from the local machine
- A unique administrator password generated on first boot and written to
/root/ersatztv-credentials.txt - A fully patched Ubuntu 24.04 LTS base with unattended security upgrades enabled
Prerequisites
- An Azure subscription and either the Azure Portal or the Azure CLI
- A network security group that allows inbound TCP 22 (SSH) and 80 (the ErsatzTV UI and IPTV surface). Optionally 443 if you add TLS for production
- Your own media files (movies, shows, music videos) to build channels from, which you can copy onto the VM or attach on a data disk after launch
Step 1 - Launch the VM
Azure Portal
- Open the ErsatzTV on Ubuntu 24.04 LTS offer from cloudimg on the Azure Marketplace and select Get It Now, then Create.
- Choose your subscription, resource group and region, and a VM size. Standard_B2s (2 vCPU, 4 GB) is a good starting point for a single software-transcoded channel; add cores if you plan to run several concurrent channels.
- Under Networking, allow inbound 22 and 80.
- Review and create, then download or select your SSH key pair.
Azure CLI
az vm create \
--resource-group my-ersatztv-rg \
--name ersatztv \
--image cloudimg:ersatztv-ubuntu-24-04:ersatztv-ubuntu-24-04:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
az vm open-port --resource-group my-ersatztv-rg --name ersatztv --port 22 --priority 1001
az vm open-port --resource-group my-ersatztv-rg --name ersatztv --port 80 --priority 1002
Accept the marketplace image terms once per subscription with az vm image terms accept --urn cloudimg:ersatztv-ubuntu-24-04:ersatztv-ubuntu-24-04:latest if prompted.
Step 2 - Confirm the services are running
SSH in as azureuser and confirm ErsatzTV and nginx are active. ErsatzTV listens on the loopback interface only (ports 8409 for management and 8410 for the streaming surface), and nginx is the single public entry point on port 80:
systemctl is-active ersatztv nginx
sudo ss -tlnp | grep -E ':(80|8409|8410) '
Both services report active. ErsatzTV is bound to 127.0.0.1-reachable ports only (an nftables guard drops any off-machine access to 8409 and 8410), and nginx listens on port 80.

Step 3 - Retrieve the per-VM administrator password
The administrator password is generated uniquely on this VM's first boot and written to a root only file. The installed ErsatzTV version and the credentials file are:
cat /opt/ersatztv/VERSION
sudo cat /root/ersatztv-credentials.txt
The file records the admin username (admin), the generated ERSATZTV_PASSWORD, the URL to open in a browser, and the unauthenticated IPTV endpoints your players use. Keep this password somewhere safe; it is not stored anywhere in the cloudimg image.

Step 4 - Confirm the health endpoint
nginx serves an unauthenticated health endpoint for load balancers and probes:
curl -s http://localhost/healthz
It returns ok. This endpoint never touches ErsatzTV and never requires authentication, so it is safe for an Azure Load Balancer health probe.
Step 5 - Verify the authentication gate from the command line
The admin UI requires the administrator password for every request, so an unauthenticated call returns 401. Confirm this, then sign in with the per-VM password read from the root only file:
# Secure by default: an unauthenticated request to the admin UI is rejected
curl -s -o /dev/null -w 'unauthenticated admin UI -> HTTP %{http_code}\n' http://localhost/
# Read the per-VM admin password from the root-only file and authenticate
PASS=$(sudo grep '^ERSATZTV_PASSWORD=' /root/ersatztv-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'wrong password -> HTTP %{http_code}\n' -u 'admin:wrong-password' http://localhost/
curl -s -o /dev/null -w 'per-VM password -> HTTP %{http_code}\n' -u "admin:$PASS" http://localhost/
The unauthenticated request and the wrong password both return 401. The per-VM password returns 200. A shipped self test at /usr/local/bin/ersatztv-selftest proves the same round-trip and is what the build verifies before capture.

Step 6 - Confirm the IPTV client surface
The endpoints your IPTV players consume, the HDHomeRun tuner, the M3U playlist and the XMLTV guide, are served without a password so Plex, Jellyfin and Emby can read them directly. The image ships one default channel so the surface is live immediately:
# The M3U playlist and XMLTV guide, served unauthenticated
curl -s http://localhost/iptv/channels.m3u | head -4
curl -s -o /dev/null -w 'xmltv.xml -> HTTP %{http_code}\n' http://localhost/iptv/xmltv.xml
# The HDHomeRun discovery endpoint (what Plex/Emby auto-detect)
curl -s -o /dev/null -w 'discover.json -> HTTP %{http_code}\n' http://localhost/discover.json
The playlist returns an #EXTM3U document, and the guide and discovery endpoints return 200. These are the same paths your players point at, using this VM's public address instead of localhost.

Step 7 - Sign in to the web admin UI
Open http://<your-vm-public-ip>/ in a browser. The browser prompts for the reverse-proxy credentials: enter admin and the ERSATZTV_PASSWORD from Step 3. ErsatzTV opens on its home page, which shows a set of self-diagnostic health checks and the release notes.

Two health checks are expected to show an amber advisory on a fresh cloud VM and are not errors: FFmpeg Version notes that the installed jellyfin-ffmpeg is newer than the exact release ErsatzTV pins (it works normally), and Hardware Acceleration notes that the default channel uses software transcoding, which is correct on a VM without a GPU.
Step 8 - Review the channels
Open Channels from the left navigation. The image ships one default channel so the tuner, playlist and guide work the moment you launch; you build your own channels here once you have added media.

Step 9 - Add a media library
ErsatzTV builds channels from libraries of your own media. Open Media Sources then Local Libraries to see the library kinds (Movies, Shows, Music Videos, Songs and more). Select Add Local Library, choose a media kind, and point it at a folder on the VM (for example a directory under /mnt where you have copied or mounted your media). ErsatzTV scans the folder, reads metadata, and makes the content available to schedule into channels.

To copy media onto the VM, use scp or rsync from your machine, or attach and mount an Azure data disk and add the mount point as a local library. Once a library has been scanned, open Scheduling to arrange the content into a channel's programme, then that channel appears in your M3U playlist and XMLTV guide automatically.
Step 10 - Build a schedule
Open Schedules from the left navigation and select Add Schedule to define what plays on a channel and when. A schedule references collections of your media and lays them out into a continuous programme that ErsatzTV streams as a live channel.

Step 11 - Connect Plex, Jellyfin or Emby
Point your IPTV client at this VM using its public address. The endpoints are unauthenticated so your player can read them directly:
- HDHomeRun tuner / device:
http://<your-vm-public-ip>/— Plex and Emby auto-detect the tuner viadiscover.json,lineup.jsonanddevice.xml - M3U playlist:
http://<your-vm-public-ip>/iptv/channels.m3u - XMLTV guide:
http://<your-vm-public-ip>/iptv/xmltv.xml
In Plex, add ErsatzTV under Live TV & DVR; it detects the HDHomeRun-style tuner and imports the XMLTV guide. In Jellyfin or Emby, add a tuner device of type M3U pointing at the playlist URL, and an XMLTV guide source pointing at the guide URL.
Security and networking notes
- Only ports 22 and 80 need to be open. Never open ports 8409 or 8410 in your network security group. ErsatzTV binds those internally, and a host firewall drops any off-machine access to them, but leaving them closed at the network layer is the correct posture. All access goes through nginx on port 80.
- The admin UI is gated; the IPTV surface is not. The reverse proxy requires the per-VM password for the management interface but intentionally leaves the tuner, playlist and guide open so your players work. If your VM is reachable from the public internet, anyone who knows the address can read your channel list and streams; restrict inbound port 80 to the networks that need it (your player, your home network) using the network security group.
- Rotate or change the password by regenerating a bcrypt entry:
sudo htpasswd -B /etc/nginx/.ersatztv.htpasswd adminand then update/root/ersatztv-credentials.txtto match.
Production: add a domain and TLS
For a production deployment, put a TLS certificate in front of nginx. Point a DNS name at the VM, open port 443, install certbot and the nginx plugin, and run sudo certbot --nginx -d your-domain to obtain and install a certificate. ErsatzTV honours the forwarded protocol and host headers, so the M3U and XMLTV URLs it generates automatically use your HTTPS domain.
Troubleshooting
- ErsatzTV service status:
systemctl status ersatztvandsudo journalctl -u ersatztv -n 100 --no-pager - First-boot log (password generation and the auth-gate proof):
sudo cat /var/log/ersatztv-firstboot.log - The admin UI returns 502 briefly after a reboot: ErsatzTV runs database migrations on start and the interface is ready within a minute; the health endpoint and IPTV surface come up once migrations finish.
- ffmpeg version: confirm the transcoder with
ffmpeg -version | head -1(it reports a 7.x jellyfin build).
Support
Every cloudimg image is backed by 24/7 support. If you have any questions about this ErsatzTV image, contact the cloudimg team through the Azure Marketplace listing.
This is a repackaged open source software product with additional charges for cloudimg support services. ErsatzTV is a trademark of its respective owner. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.