ErsatzTV
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, and streams them 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 with a proper channel guide.
This SecureAnyCloud 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. A web interface lets you connect media libraries, build channels, arrange schedules and tune ffmpeg transcoding, and a REST API is available for automation.
ErsatzTV has no login of its own on its management interface, which would leave a bare deployment open to anyone who can reach it. This image does not ship in that state. ErsatzTV listens only on the local machine, and an nginx reverse proxy on port 80 is the only public entry point. The proxy exposes the administration interface only after a unique administrator password is generated on each VM's first boot, so there is never a window in which an unauthenticated interface is exposed to the network.
What you get:
- 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, the modern ffmpeg build ErsatzTV recommends, installed from an apt repository so it keeps receiving security updates and 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, hashed for the reverse proxy and written in plain form to the root-only file
/root/ersatztv-credentials.txt, so no two deployments share a credential - Open tuner, playlist and guide endpoints, so Plex, Jellyfin and Emby keep working while the admin interface stays protected
- An Ubuntu 24.04 LTS base, patched at build time, with unattended security upgrades enabled
- 24/7 SecureAnyCloud support
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 allow 443 if you add TLS for production.
- Your own media files (movies, shows, music videos) to build channels from. You can copy them onto the VM or attach them on a data disk after launch.
Step 1 - Launch the VM
Azure Portal
- Open the ErsatzTV on Ubuntu 24.04 LTS offer from SecureAnyCloud on the Azure Marketplace and select Get It Now, then Create.
- Choose your subscription, resource group, region and VM size. Standard_B2s (2 vCPU, 4 GB) is a good starting size for a single software-transcoded channel. Add cores if you plan to run several concurrent channels.
- Under Networking, allow inbound 22 and 80.
- Select Review + create, then download or select your SSH key pair.
Azure CLI
az vm create \
--resource-group my-ersatztv-rg \
--name ersatztv \
--image sac1647283583153:ersatztv-ubuntu-24-04:default: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 1002If prompted, accept the Marketplace image terms once per subscription with az vm image terms accept --urn sac1647283583153:ersatztv-ubuntu-24-04:default:latest.
Step 2 - Confirm the services are running
SSH in as azureuser and confirm that ErsatzTV and nginx are active. ErsatzTV listens on the loopback interface only, on port 8409 for management and port 8410 for the streaming surface. 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 reachable on 127.0.0.1 only, because an nftables guard drops any off-machine access to ports 8409 and 8410. nginx listens on port 80.
Step 3 - Retrieve the per-VM administrator password
First boot generates a unique administrator password on this VM and writes it to a root-only file. Print the installed ErsatzTV version and the credentials file:
cat /opt/ersatztv/VERSION
sudo cat /root/ersatztv-credentials.txtThe 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 SecureAnyCloud image.
Step 4 - Confirm the health endpoint
nginx serves an unauthenticated health endpoint for load balancers and probes. Query it:
curl -s http://localhost/healthzIt returns ok. This endpoint never touches ErsatzTV and never requires authentication, so it is safe to use as 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.
The self-test at /usr/local/bin/ersatztv-selftest checks 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.jsonThe playlist returns an #EXTM3U document, and the guide and discovery endpoints return 200. These are the same paths your players use, with this VM's public address in place of localhost.
Step 7 - Sign in to the web admin UI
Open http://<your-vm-public-ip>/ in a browser. When 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 self-diagnostic health checks and the release notes. Two health checks show an amber advisory on a fresh cloud VM. Neither is an error:
- FFmpeg Version notes that the installed jellyfin-ffmpeg is newer than the exact release ErsatzTV pins. It works normally.
- 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. 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. Alternatively, 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. The channel then 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 throughdiscover.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. Plex detects the HDHomeRun-style tuner and imports the XMLTV guide.
In Jellyfin or Emby, add a tuner device of type M3U that points at the playlist URL. Then add an XMLTV guide source that points at the guide URL.
Security and networking notes
- Open only ports 22 and 80. Never open ports 8409 or 8410 in your network security group. ErsatzTV binds them to the local machine and a host firewall drops any off-machine access, but keeping 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 it 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. Use the network security group to restrict inbound port 80 to the networks that need it, such as your player and your home network.
- Rotate or change the password by regenerating a bcrypt entry with
sudo htpasswd -B /etc/nginx/.ersatztv.htpasswd admin. 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 and open port 443. Then install certbot and the nginx plugin, and run sudo certbot --nginx -d your-domain to obtain and install a certificate.
ErsatzTV honors 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 SecureAnyCloud image is backed by 24/7 support. If you have questions about this ErsatzTV image, contact the SecureAnyCloud team through the Azure Marketplace listing.
This is a repackaged open-source software product, with additional charges for SecureAnyCloud support services. ErsatzTV is a trademark of its respective owner. All product and company names are trademarks or registered trademarks of their respective holders, and their use does not imply any affiliation with or endorsement by them.
Deploy on Azure
Launch ErsatzTV on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com
ErsatzTV is an open-source, self-hosted IPTV server. It turns your own local media, such as movies, shows and music videos you supply, into live, always-on television channels that play on a schedule you define, and streams them 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 other IPTV clients can add it as a live TV source with a proper channel guide.
This image delivers ErsatzTV 26.6.0 fully installed, secured and reverse-proxied with nginx. ErsatzTV is a self-contained .NET application that runs under systemd as a dedicated unprivileged ersatztv service account. All state, including its SQLite database, configuration and transcode scratch, lives under /var/lib/ersatztv. Transcoding uses a clean, redistributable GPL FFmpeg 7.x build baked into the image, with the H.264 (libx264) and AAC encoders ErsatzTV needs.
You supply the media. This image ships ErsatzTV with no media of any kind. You add your own content and you are responsible for holding the rights to stream it. Use ErsatzTV only with media you are lawfully entitled to use.
Secure by default
ErsatzTV has no built-in authentication on its management interface, and its internal web server binds all interfaces. A bare deployment therefore leaves an unauthenticated admin UI reachable on the network, and anyone who can reach it gets full control of your channels and libraries. This image never ships in that state:
- ErsatzTV listens only on the loopback interface (management port
8409, streaming port8410). A host firewall (nftables) drops any off-loopback access to those ports. - An nginx reverse proxy on port 80 is the only public entry point. HTTP Basic Auth protects the admin UI and REST API.
- A unique administrator password is generated on each instance's first boot and bcrypt-hashed for nginx. The plaintext is written only to a root-only file. No shared or default password ships in the image, and nginx does not start until that password is in place, so an unauthenticated admin UI is never exposed.
- The IPTV client endpoints stay open without a password: the tuner, the M3U playlist and the XMLTV guide. Plex, Jellyfin and Emby keep working while the admin interface stays protected.
- An unauthenticated
/healthzendpoint is provided for load-balancer probes.
Because the endpoints split this way, open only ports 22 and 80 in your security group, and optionally 443 for TLS. Never expose 8409 or 8410.
Connecting to your instance
SSH in as the default login user for your OS variant:
OS variant SSH login user
Ubuntu 24.04 ubuntu
ssh ubuntu@<public-ip>Retrieve the generated administrator password, which only root can read:
sudo cat /root/ersatztv-credentials.txtThe file shows the per-instance credentials and the endpoint URLs, for example:
ERSATZTV_URL=http://203.0.113.10/ ERSATZTV_USERNAME=admin ERSATZTV_PASSWORD=<set-at-first-boot>Open ERSATZTV_URL in a browser and sign in as admin with that password to reach the ErsatzTV web admin UI.
Verifying the deployment
Confirm the services are active:
systemctl is-active ersatztv.service nginx.service ersatztv-firewall.serviceAll three report active. Confirm that the unauthenticated health endpoint answers:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthzThis returns 200. The admin UI, by contrast, is refused without the password:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/This returns 401, which shows that the admin interface is gated.
The IPTV endpoints (tuner, M3U and XMLTV)
ErsatzTV publishes an HDHomeRun tuner, an M3U playlist and an XMLTV guide. These are served without a password so your players can consume them. Fetch the playlist:
curl -s http://127.0.0.1/iptv/channels.m3u | head -3Until you create your own channels, ErsatzTV publishes a single built-in channel:
#EXTM3U url-tvg="http://127.0.0.1/iptv/xmltv.xml" x-tvg-url="http://127.0.0.1/iptv/xmltv.xml" #EXTINF:0 tvg-id="C1.145.ersatztv.org" channel-number="1" tvg-chno="1" tvg-name="ErsatzTV" group-title="ErsatzTV", ErsatzTV http://127.0.0.1/iptv/channel/1.tsThe matching guide data is at the XMLTV endpoint:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/iptv/xmltv.xmlThis returns 200. From your own network, point your IPTV client at the same host using the instance's address:
- HDHomeRun tuner / device:
http://<public-ip>/ - M3U playlist:
http://<public-ip>/iptv/channels.m3u - XMLTV guide:
http://<public-ip>/iptv/xmltv.xml
Adding your media and building a channel
- In the ErsatzTV UI, open Media Sources -> Local and add a library that points at a folder of your own media. First copy or mount your content onto the instance, for example under
/srv/media, and make sure theersatztvuser can read it. ErsatzTV scans the folder and imports the items. - Open Channels and choose Add Channel. Give the channel a number and a name, and pick the FFmpeg profile. A default
1920x1080 x264 aacprofile ships with the image. - Use Lists (collections) and Scheduling to decide what plays on the channel and in what order, then build the channel's playout.
- Your new channel now appears in the M3U playlist and XMLTV guide automatically, ready for your IPTV clients.
The image ships a clean, redistributable GPL FFmpeg 7.x build. The default transcode profile streams H.264 video with AAC audio.
Connecting Plex, Jellyfin or Emby
In your media server's Live TV / DVR setup, add an HDHomeRun tuner and point it at http://<public-ip>/. Then supply the XMLTV guide URL http://<public-ip>/iptv/xmltv.xml.
Plex, Jellyfin and Emby all consume ErsatzTV this way. Your ErsatzTV channels then appear in the client's live TV guide alongside any other tuners.
Enabling HTTPS (recommended for production)
The image serves the admin UI and IPTV surface over HTTP on port 80. For production, terminate TLS in front of ErsatzTV. Two common options:
- AWS load balancer or CloudFront: put an Application Load Balancer or CloudFront distribution in front of the instance, attach an ACM certificate, and forward to the instance on port 80.
- Let's Encrypt on the instance: point a DNS name at the instance and open port 443 in the security group. Then run:
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.example.comcertbot obtains a certificate and reconfigures nginx to serve the admin UI and IPTV endpoints over HTTPS.
Service management
ErsatzTV and its reverse proxy run as ordinary systemd services:
systemctl status ersatztv.service
sudo systemctl restart nginx.serviceConfiguration and data live under /var/lib/ersatztv, which holds the SQLite database, config and transcode scratch. The nginx site is at /etc/nginx/sites-available/sac-ersatztv. The loopback firewall guard is ersatztv-firewall.service.
Support
SecureAnyCloud provides 24/7 technical support for this product by email and live chat at sales@secureanycloud.com.
We can help with deployment, connecting media libraries, building channels and schedules, ffmpeg transcoding, TLS termination and integrating Plex, Jellyfin and Emby.
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com