Kiln uses two layers to manage outbound traffic: [[proxies]] defines reusable proxies, and [egress] decides which requests use each route. Stream sources, guide sources, and logo requests share one routing table, so you do not need to configure them separately.
The model
Every outbound URL is resolved on its own:
Match rules by priority
Rules are evaluated in ascending priority order. The first match wins and evaluation stops there.
Fall back to the default
When nothing matches, egress.default applies.
Resolve the route
direct connects directly. A proxy ID uses that route. If the referenced route is missing or disabled, the request falls back to a direct connection.
[[proxies]]
id = "proxy-http"
name = "HTTP route"
url = "http://127.0.0.1:7890"
[[proxies]]
id = "proxy-socks"
name = "SOCKS5 route"
url = "socks5h://127.0.0.1:7891"
[egress]
default = "direct"
playlist_policy = "rewrite"
# docker_proxy_host = "host.docker.internal"
[[egress.rules]]
id = "example-cdn"
priority = 10
kind = "host_suffix"
pattern = "example-cdn.com"
proxy = "proxy-http"Defining a route
idmust be unique and is whategress.default, rules, and EPG sources reference.nameis for display only.urlaccepts thehttp,https,socks5andsocks5hschemes, for examplehttp://127.0.0.1:7890orsocks5h://127.0.0.1:7891.- Put credentials in the URL when the proxy needs them, for example
http://user:password@127.0.0.1:7890. The console shows that a route has credentials without echoing the password back. socks5hresolves hostnames at the proxy,socks5resolves them locally first. Prefersocks5hwhen the origin only resolves correctly from the proxy’s network.- Routes can be disabled instead of deleted. A route referenced by
egress.defaultor by an enabled rule cannot be left disabled.
Default egress and rules
egress.default is either direct or the ID of a route, and covers everything no rule matched.
A rule consists of id, priority, kind, pattern, and proxy, and can be disabled individually. Lower priority numbers win. Set proxy to direct to create a direct exception in a deployment that otherwise proxies everything.
kind |
Matches against |
|---|---|
host_suffix |
Hostname suffix. example.com matches example.com and cdn.example.com, but not notexample.com |
host_exact |
The full hostname, exact comparison |
host_regex |
A regular expression over the hostname |
channel_id |
Exact channel ID |
url_regex |
A regular expression over the full URL |
An omitted kind is treated as host_suffix. Regex rules are compiled before they are saved, and a rule that fails to compile never takes effect.
Playlist handling
playlist_policy controls how absolute origin URLs are presented to the player when Kiln relays an upstream HLS playlist.
| Value | Behavior |
|---|---|
rewrite (default) |
Everything is rewritten back to Kiln; the player talks only to Kiln |
passthrough |
Origin URLs are left untouched; the player connects to the origin |
auto |
Only URLs that need a proxy are rewritten, the rest stay as origin URLs |
Under rewrite, Kiln resolves every media line and every URI="..." attribute to an absolute URL, then replaces it with /v1/play/<channel id>/u/<encoded url>?sig=... on its own origin. Behind an access token the prefix becomes /p/<token>/play/<channel id>/u/ instead. The URL is encoded with URL-safe Base64 and keeps a recognizable extension, and the signature is an HMAC over the channel ID and target URL, keyed by a random value generated at process start, so rewritten URLs stop working after a restart. The player therefore only ever contacts Kiln, and Kiln performs the upstream fetch through whichever route the rules selected. Nested variant playlists are rewritten again as they are pulled.
passthrough sends the player straight to the origin. Those requests bypass Kiln’s proxy routes and never carry the channel’s custom headers, so it only makes sense when the player itself can reach the origin. auto splits the difference: URLs that need a proxy come back through Kiln, everything else keeps its origin address and saves a hop.
Reaching a proxy from inside a container
Kiln never rewrites the address on a route. When it runs in a container, url has to be reachable from inside the Kiln container: the 127.0.0.1 that works on the host points at the container itself, so copying it over will not connect.
The proxy runs on the host
Use host.docker.internal to reach back to the host. Docker Desktop provides that name; on Linux you have to map it to the gateway yourself:
services:
kiln:
image: ghcr.io/babywbx/kiln:core
ports:
- "8080:8080"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./kiln.toml:/etc/kiln/kiln.toml:ro
- kiln-data:/var/lib/kiln/data
restart: unless-stopped
volumes:
kiln-data:Point the route at that hostname, with the port the proxy listens on over on the host:
[[proxies]]
id = "proxy-host"
name = "Host proxy"
url = "http://host.docker.internal:7890"The proxy is another container
With both containers on the same Docker network, address the proxy by service name; it does not need to publish a port to the host at all:
services:
proxy:
image: your-proxy-image
container_name: proxy
volumes:
- ./proxy-config:/etc/proxy:ro
restart: unless-stopped
networks: [kiln-net]
kiln:
image: ghcr.io/babywbx/kiln:core
depends_on: [proxy]
ports:
- "8080:8080"
volumes:
- ./kiln.toml:/etc/kiln/kiln.toml:ro
- kiln-data:/var/lib/kiln/data
restart: unless-stopped
networks: [kiln-net]
networks:
kiln-net:
volumes:
kiln-data:Use the service name plus the port the proxy listens on inside its own container, which is not the port it may publish to the host:
[[proxies]]
id = "proxy-sidecar"
name = "Proxy container"
url = "http://proxy:7890"
[[proxies]]
id = "proxy-sidecar-socks"
name = "Proxy container SOCKS"
url = "socks5h://proxy:7891"Traffic within one network never leaves the host, so the proxy container needs neither a published port nor extra_hosts.
The proxy is on another machine on the LAN
This one needs nothing special on the Docker side. The default bridge network already reaches the LAN the host sits on, so just point the route at that machine:
[[proxies]]
id = "proxy-lan"
name = "LAN proxy"
url = "http://192.168.1.100:7890"No extra_hosts, no shared network; it is written exactly as it would be with Kiln running directly on the host. The requirement is the same as before: the proxy over there has to listen on 0.0.0.0 or on the relevant interface, with the port open through that machine’s firewall.
docker_proxy_host plays no part in this
docker_proxy_host only applies when ffmpeg.mode = "docker", where it tells the ffmpeg container Kiln spawns how to reach the Kiln process itself. It has nothing to do with route addresses, and neither topology above needs it changed.
Verifying
The Outbound proxies page refuses to apply a route until one connectivity test passes, and that test runs from inside the Kiln container, so a pass means the address is reachable from where it matters. When it fails, work through it in this order: whether the proxy accepts non-local connections, whether the container resolves that hostname or service name, and whether the port is the one seen from inside the container.
What outbound routing covers
| Traffic | Notes |
|---|---|
| Channel origin fetches | Playlists and segments, on both the native and the ffmpeg path |
| EPG fetches | Routed by the rules only when the source’s egress is auto; direct or a named route is honored as configured, see EPG |
| Channel logos | The upstream fetch behind /v1/logo/{id}, matched with the channel ID in scope |
| Admin probes | Channel probe, preview, and the outbound connectivity test |
channel_id rules can only match when the request carries a channel context. EPG fetches have none, so they are never selected by that rule kind.
Console and connectivity test
The Outbound proxies page edits a draft. Adding or changing routes and rules only touches the draft, and Apply stays disabled until a connectivity test passes; any further edit resets that state and requires a new test. It is a cheap way to avoid pushing a typo in a proxy address to a running service.
The test maps to POST /v1/admin/egress/test and needs the refresh scope. Optional parameters:
target: omit it to use the built-in public probe target.sourceorcustomrequires aurl, which must resolve to a public address.channel_id: resolve the route as that channel would, which is how you verifychannel_idrules.proxy_idorproxy_url: test one saved route, or an address that has not been saved yet.draft: resolve against the draft configuration without touching the running one.
The response reports the selected proxy_id, the matching reason, whether playlists would be rewritten, the elapsed dur_ms, and reachability with HTTP status and final URL. A proxy demanding authentication is called out explicitly.
Common topologies
Direct by default, proxy a few hosts
Keep default = "direct" and add one host_suffix rule per origin that needs the proxy. The right shape when most origins are reachable and only a few are not.
Proxy by default, a few direct
Set default = "proxy-http", then add rules with proxy = "direct" for LAN or local origins. Internal upstreams stay off the proxy, which never sees your internal addresses.
Kiln in a container, proxy on the host
Point the route at http://host.docker.internal:7890 and give the Kiln container a host.docker.internal:host-gateway mapping. See Reaching a proxy from inside a container.
Kiln and the proxy in separate containers
Put both on one Docker network and point the route at http://<service>:<internal port>; the proxy publishes nothing. See Reaching a proxy from inside a container.
For diagnosing a route that behaves unexpectedly, see Troubleshooting.