iptv-proxy
iptv-proxy is a reverse proxy for IPTV: give it an M3U/M3U8 playlist or an Xtream Codes account, and it serves the same channels, movies and series from your own address, with a user and a password you choose.
Your players (TiviMate, IPTV Smarters, VLC, Kodi, Plex or Jellyfin through an M3U playlist...) talk to the proxy and never see the provider's address or credentials. Use it to share IPTV at home without handing out the provider's login, to put a provider behind your own domain, HTTPS or VPN, or to give every device one address that does not change.
It is one binary or one container, set up with a few flags or environment variables. There is no database and nothing to configure in a web interface.
Quick start
In the examples, 192.168.1.10 is the machine running the proxy, as your
players reach it, and family / choose-a-password are the credentials you
give to your players. Replace them with your own.
Docker, with an M3U playlist
docker run -d --name iptv-proxy -p 8080:8080 \
-e M3U_URL="http://provider.example:8080/playlist.m3u" \
-e PROXY_HOSTNAME=192.168.1.10 \
-e PROXY_USER=family \
-e PROXY_PASSWORD=choose-a-password \
-e GIN_MODE=release \
pierro777/iptv-proxy:latest
Give this address to your player:
http://192.168.1.10:8080/iptv.m3u?username=family&password=choose-a-password
To check it from a terminal:
curl "http://192.168.1.10:8080/iptv.m3u?username=family&password=choose-a-password"
The playlist can also be a local file: mount it and give its path.
docker run -d --name iptv-proxy -p 8080:8080 \
-v "$PWD/iptv.m3u:/iptv.m3u:ro" \
-e M3U_URL=/iptv.m3u \
-e PROXY_HOSTNAME=192.168.1.10 \
-e PROXY_USER=family \
-e PROXY_PASSWORD=choose-a-password \
-e GIN_MODE=release \
pierro777/iptv-proxy:latest
Docker, with an Xtream Codes account
docker run -d --name iptv-proxy -p 8080:8080 \
-e XTREAM_BASE_URL="http://provider.example:8080" \
-e XTREAM_USER=xtream_user \
-e XTREAM_PASSWORD=xtream_password \
-e PROXY_HOSTNAME=192.168.1.10 \
-e PROXY_USER=family \
-e PROXY_PASSWORD=choose-a-password \
-e GIN_MODE=release \
pierro777/iptv-proxy:latest
In your player, choose the Xtream Codes login and enter:
Server: http://192.168.1.10:8080
Username: family
Password: choose-a-password
A player that only takes a playlist can use the usual Xtream addresses:
Playlist: http://192.168.1.10:8080/get.php?username=family&password=choose-a-password&type=m3u_plus&output=ts
Guide: http://192.168.1.10:8080/xmltv.php?username=family&password=choose-a-password
Docker images
| Registry | Image |
|---|---|
| Docker Hub | pierro777/iptv-proxy |
| GitHub | ghcr.io/pierre-emmanuelj/iptv-proxy |
Tags: latest, and for a given version v3.12.0, v3.12 or v3. Since
v3.12.0 they work on amd64 and on ARM (Raspberry Pi, Apple silicon): Docker
pulls the image of your machine. The -amd64 and -arm64 tags
(pierro777/iptv-proxy:latest-arm64) name one architecture, as before.
Docker Compose
services:
iptv-proxy:
image: pierro777/iptv-proxy:latest
restart: unless-stopped
ports:
# the same port as PORT below
- 8080:8080
environment:
# Either an M3U playlist (an address, or a file mounted in the container)...
M3U_URL: "http://provider.example:8080/playlist.m3u"
# ...or an Xtream Codes account (remove M3U_URL then)
# XTREAM_BASE_URL: "http://provider.example:8080"
# XTREAM_USER: xtream_user
# XTREAM_PASSWORD: xtream_password
PORT: 8080
# Name or IP your players reach this machine at
PROXY_HOSTNAME: 192.168.1.10
# What your players log in with
PROXY_USER: family
PROXY_PASSWORD: choose-a-password
GIN_MODE: release
docker compose up -d
The docker-compose.yml of this repository does the
same, building the image from the sources.
Binary
Download the archive for your system (Linux, macOS, Windows; amd64 and arm64;
also .deb and .rpm) from the
releases,
or build it with Go 1.27 or later:
git clone https://github.com/pierre-emmanuelJ/iptv-proxy.git
cd iptv-proxy
go build -o iptv-proxy .
GIN_MODE=release ./iptv-proxy \
--m3u-url "http://provider.example:8080/playlist.m3u" \
--hostname 192.168.1.10 \
--user family \
--password choose-a-password
Quote the playlist address: it usually contains ? and &.
Options
Every option is a flag, an environment variable (the flag's name in upper
case, with _ instead of -) or a line of the configuration file. A flag
wins over the variable, and the variable over the file.
| Flag | Environment variable | Default | What it does |
|---|---|---|---|
--m3u-url, -u |
M3U_URL |
Provider playlist: an http(s) address or a local file. |
|
--xtream-base-url |
XTREAM_BASE_URL |
Provider's Xtream Codes address, e.g. http://provider.example:8080. |
|
--xtream-user |
XTREAM_USER |
Provider's Xtream Codes user. | |
--xtream-password |
XTREAM_PASSWORD |
Provider's Xtream Codes password. | |
--hostname |
PROXY_HOSTNAME (or HOSTNAME) |
Name or IP your players reach the proxy at. It is written in every address the proxy gives out. | |
--port |
PORT |
8080 |
Port the proxy listens on. |
--listen-address |
LISTEN_ADDRESS |
every interface | IP address the proxy listens on, e.g. 127.0.0.1 behind a reverse proxy on the same machine. |
--advertised-port |
ADVERTISED_PORT |
value of --port |
Port written in the addresses the proxy gives out, when it differs from the listening port (behind a reverse proxy, or a different published Docker port). |
--https |
HTTPS |
false |
Write https:// instead of http:// in the addresses the proxy gives out. The proxy itself always listens in plain HTTP: put a reverse proxy in front for TLS. |
--user |
PROXY_USER (or USER) |
usertest |
User your players log in with. |
--password |
PROXY_PASSWORD (or PASSWORD) |
passwordtest |
Password your players log in with. |
--m3u-file-name |
M3U_FILE_NAME |
iptv.m3u |
Name of the playlist the proxy serves: http://host:port/iptv.m3u. |
--custom-endpoint |
CUSTOM_ENDPOINT |
Prefix put before every path: http://host:port//iptv.m3u. |
|
--custom-id |
CUSTOM_ID |
random | First path element of M3U track addresses. Random at each start by default; set it to keep the same track addresses across restarts. |
--m3u-cache-expiration |
M3U_CACHE_EXPIRATION |
1 |
Hours a playlist fetched from an Xtream provider is kept before asking for it again. |
--xtream-api-get |
XTREAM_API_GET |
false |
Build the get.php playlist (live channels) from the provider's API, for providers that disabled get.php. |
--xtream-api-get-movies |
XTREAM_API_GET_MOVIES |
false |
Add the provider's movies to that generated playlist. Off by default: a catalogue of tens of thousands of movies makes a playlist some players cannot load. Series are not added (see below). |
--user-agent |
USER_AGENT |
User-Agent sent to the provider instead of the player's. Some providers only answer known players. | |
--no-stream-sharing |
NO_STREAM_SHARING |
false |
Open one provider connection per player for a live stream, instead of sharing one between the players watching it. |
--group-regex |
GROUP_REGEX |
Keep only the live channels whose group matches this regular expression. See Filtering channels. | |
--channel-regex |
CHANNEL_REGEX |
Keep only the live channels whose name matches this regular expression. | |
--group-exclude-regex |
GROUP_EXCLUDE_REGEX |
Leave out the live channels whose group matches this regular expression. | |
--channel-exclude-regex |
CHANNEL_EXCLUDE_REGEX |
Leave out the live channels whose name matches this regular expression. | |
--iptv-proxy-config |
.iptv-proxy.yaml in the home or current directory |
YAML file holding the same options, named as the flags (m3u-url: ...). |
Good to know:
- Change
--userand--password: the defaults are public. USERandHOSTNAMEare also ordinary system variables: a shell setsUSERto your login name, and Docker setsHOSTNAMEto the container's ID. Since v3.11.0, preferPROXY_USER,PROXY_PASSWORDandPROXY_HOSTNAME: they win over the old names, which keep working. Since v3.12.0,user,passwordandhostnamein the configuration file also win over the old names.GIN_MODEis not an option of the proxy but of its web framework. Since v3.10.0 the proxy runs inreleasemode unless you set it. Before that, setGIN_MODE=releaseas the examples do: the debug mode lists the routes at startup, and they contain your user and password. The access log masks them in both modes.
M3U playlists
The proxy reads the provider's playlist once, at startup, and serves it at
/iptv.m3u with every track address replaced by its own. All other lines
(names, logos, groups, guide IDs, player options) are kept as the provider
wrote them. Restart the proxy to pick up a new version of the playlist.
The provider's playlist:
#EXTM3U
#EXTINF:-1 tvg-id="news.example" tvg-name="News" tvg-logo="http://provider.example/logos/news.png" group-title="News",News HD
http://provider.example:8080/live/news/1.ts
#EXTINF:-1 tvg-id="sport.example" tvg-name="Sport" tvg-logo="http://provider.example/logos/sport.png" group-title="Sport",Sport HD
http://provider.example:8080/live/sport/2.m3u8?token=abc
What your players get:
#EXTM3U
#EXTINF:-1 tvg-id="news.example" tvg-name="News" tvg-logo="http://provider.example/logos/news.png" group-title="News",News HD
http://192.168.1.10:8080/e3c0c308/family/choose-a-password/0/1.ts
#EXTINF:-1 tvg-id="sport.example" tvg-name="Sport" tvg-logo="http://provider.example/logos/sport.png" group-title="Sport",Sport HD
http://192.168.1.10:8080/e3c0c308/family/choose-a-password/1/2.m3u8
e3c0c308 is the --custom-id. Tokens and other query parameters of the
provider's addresses stay on the proxy.
Some playlists name the provider's credentials outside of the track
addresses too: a guide address in url-tvg, a catch-up address in
catchup-source, a player option. Such an attribute or line is removed, so
that the credentials never reach a player. (In an Xtream Codes playlist, they
point to the proxy instead: see below.)
Xtream Codes
The proxy answers the Xtream Codes client API like the provider does: live, movies, series, catch-up and the guide. Your players log in with the proxy's address and credentials, the proxy asks the provider with the real ones.
| Provider | Proxy | |
|---|---|---|
| Server | http://provider.example:8080 |
http://192.168.1.10:8080 |
| User | xtream_user |
family |
| Password | xtream_password |
choose-a-password |
The provider's answers are passed on as they are, so the fields and quirks of
any provider reach the player. Only what names the provider is rewritten: the
account and server of the login answer, and the addresses holding its
credentials. In the get.php playlist, the guide (url-tvg) and catch-up
(catchup-source) addresses of the provider become the proxy's, and work
through it.
Endpoints served:
| Endpoint | What it is |
|---|---|
/player_api.php |
The client API (login, categories, streams, movie and series details, short guide). |
/get.php |
The M3U playlist, with the same parameters as the provider's (type, output). |
/xmltv.php |
The full guide (XMLTV), streamed from the provider. |
/apiget |
A playlist of the live channels built from the API. |
///, /live/..., /movie/..., /series/..., /timeshift/... |
The streams. |
/player_api.php, /get.php, /xmltv.php and /apiget take
username=...&password=....
If you give --m3u-url the provider's get.php address
(http://provider.example:8080/get.php?username=xtream_user&password=xtream_password&type=m3u_plus&output=ts),
the Xtream options are read from it: everything above is served, and that
playlist is also available at /iptv.m3u.
With the Xtream options alone, /iptv.m3u is the account's playlist too (the
same as /get.php).
The playlist built from the API (/apiget, or /get.php with
--xtream-api-get) holds the live channels, and the movies with
--xtream-api-get-movies. Series are not in it: the API gives the episodes of
one series at a time, so listing them all would take one request per series.
Players that speak the Xtream API get movies and series from it directly.
HLS
HLS streams (.m3u8) work in both modes, whatever the provider. Every address
an HLS playlist names (variants, segments, keys, audio tracks) is replaced by
an address of the proxy, /hls//, so the whole stream goes
through the proxy, on any host and after any redirect. The token is the
provider's address, encrypted: a player never sees the provider's host,
credentials or session tokens. Nothing is stored, and tokens stay valid across
restarts.
Filtering channels
Four options keep the live channels you want and leave out the others. Each
takes a regular expression matched
against a channel's group (group-title in a playlist, the category in the
Xtream API) or its name:
--group-regex,--channel-regex: keep only what matches.--group-exclude-regex,--channel-exclude-regex: leave out what matches, even if it was kept by the first two.
A channel is kept when it passes all of them. The expressions are case
sensitive; start one with (?i) to ignore case.
-e GROUP_REGEX='^(FR|UK) ' \
-e GROUP_EXCLUDE_REGEX='(?i)adult|xxx' \
-e CHANNEL_EXCLUDE_REGEX='(?i)backup|test' \
The filters apply to:
- the M3U playlist (
/iptv.m3u) and the Xtream playlists (/get.php,/apiget): a channel left out is not in it, and in M3U mode it has no address on the proxy; - the live categories and channels of the Xtream API;
- the guide (
/xmltv.php): only the channels kept, and their programmes, are in it. A guide of thousands of channels shrinks to the ones you watch.
Movies and series are not filtered. In a get.php playlist that holds movies,
the same rules apply to them, by their group and name.
The filters choose what players are shown; they are not access control. A player that already knows the id of a channel left out can still play it through the Xtream API.
Live streams: one connection to the provider
An IPTV account allows a few connections at a time, often a single one. When several players watch the same live channel, the proxy opens one connection to the provider and shares it: the first player opens the stream, the next ones join it where it is, and the connection is closed when the last one leaves.
If the provider drops the stream, or leaves it silent for 20 seconds, the proxy opens it again while someone is watching, so the player does not have to reconnect. After a few attempts that fail, the stream ends.
Movies, series and catch-up are files: each player reads its own, from where
it wants. --no-stream-sharing gives every player its own connection for live
streams too.
Behind a reverse proxy, with HTTPS
The proxy listens in plain HTTP. To serve it on your own domain with HTTPS, put a reverse proxy (Traefik, Caddy, nginx...) in front, and tell iptv-proxy which addresses to give out:
environment:
PORT: 8080 # where iptv-proxy listens
PROXY_HOSTNAME: iptv.example.com
ADVERTISED_PORT: 443 # the port your players use
HTTPS: 1 # addresses given out start with https://
Your players then use https://iptv.example.com:443/iptv.m3u?username=...&password=....
The proxy's user and password travel in every address, so use HTTPS as soon as the proxy is reachable from the Internet.
A complete example with Traefik and Let's Encrypt is in the
traefik folder. From the root of the repository:
cp -r ./traefik/* .
mkdir -p Traefik/etc/traefik Traefik/log
Then replace iptv.proxyexample.xyz with your domain in docker-compose.yml,
set your e-mail address in Traefik/traefik.yaml, and start it:
docker compose up -d
What it does not do
- It does not provide channels or streams: you need a playlist or an account from a provider.
- It does not edit the catalogue: no channel editor, no renaming, no guide mapping. Players get what the provider sends.
- It does not transcode, record or cache streams: they are passed on as they come.
- It has one provider and one user and password, shared by all your players.
- It has no web interface.
Roadmap
Planned, not available yet:
- Keeping the last good playlist, lists and guide when the provider fails, and reloading an M3U playlist without a restart.
- HDHomeRun emulation, for Plex.
- Several users, each with their own credentials and limits.
- Several providers behind one proxy, with failover.
See the changelog for what each release brought.
License
If the project is useful to you, you can buy me a beer:
