Skip to content

Outbound proxies

Configure reusable proxies and routing rules that control how Kiln connects to stream sources, guide sources, and logo files.

Updated View as Markdown

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.

kiln.tomltoml
[[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

  • id must be unique and is what egress.default, rules, and EPG sources reference. name is for display only.
  • url accepts the http, https, socks5 and socks5h schemes, for example http://127.0.0.1:7890 or socks5h://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.
  • socks5h resolves hostnames at the proxy, socks5 resolves them locally first. Prefer socks5h when the origin only resolves correctly from the proxy’s network.
  • Routes can be disabled instead of deleted. A route referenced by egress.default or 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:

compose.yamlyaml
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:

kiln.tomltoml
[[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:

compose.yamlyaml
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:

kiln.tomltoml
[[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:

kiln.tomltoml
[[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. source or custom requires a url, which must resolve to a public address.
  • channel_id: resolve the route as that channel would, which is how you verify channel_id rules.
  • proxy_id or proxy_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.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close