Skip to content

CLI & Environment

Everything tunlx can be told at startup, including flags, environment variables, and the file paths it cares about.

Command-line flags

./tunlx -configFile=/etc/tunlx/config.json
Flag Default Purpose
-configFile ./config.json Path to the JSON configuration. Auto-created on first run if missing, together with its directory.
-dashboardPort 6060 Override the dashboard port without editing config.
-thisServerHost — Override proxies[].thisServerHost for the legacy single-proxy mode.
-thisServerPort — Same, but for the listen port.
-targetServer — Same, but for the upstream URL.
-debug / -v false Verbose logging at startup. Equivalent to TUNLX_DEBUG=true.
--xtremeWizard — Launch the interactive Xtream CLI wizard and exit.
-resetAdminPassword — Set a new admin password in the config file, print it and exit. Stop tunlx first. See Troubleshooting.
-version — Print the version and exit. The dashboard's About dialog and footer show the same value; quote it when you report a problem.
-help — Print the usage text and exit.

Environment variables

Environment overrides are useful for container deployments and CI.

Core

Variable Equivalent in config Purpose
TUNLX_DEBUG — true enables verbose startup logs.
TUNLX_DASHBOARD_PORT dashboardPort Dashboard port.
TUNLX_SESSION_SECRET sessionSecret 32-byte key (base64 / hex / 32-char ASCII) for session cookies.
TUNLX_ASSETS_DIR — Read templates/ and static/ from this directory instead of the copies built into the binary. For working on the web UI; leave it unset otherwise.
TUNLX_DISABLE_EXTERNAL_IP_LOOKUP — true stops tunlx asking outside services (api.ipify.org and its fallbacks) for its public IP. The navbar then shows no address. See Networking.
TUNLX_ADMIN_PASSWORD webCredentials.password Admin password (8–72 bytes) for a first start with no config, or for -resetAdminPassword. Without it a random one is generated, printed and saved as initial-admin-password.txt next to the config, and nothing but choosing a new one opens until it is changed. A password set here is yours: nothing asks you to change it.

WireGuard

Variable Equivalent in config Purpose
TUNLX_WG_IFACE — Interface name when route: vpn (default wg0).
TUNLX_WG_CONF vpn.manual.configPath Path to the WireGuard config (default /etc/wireguard/<iface>.conf).
TUNLX_WG_AUTOSTART vpn.autoStartOnBoot Enable the systemd unit for the interface (Linux).
TUNLX_WG_MARK — Firewall mark (SO_MARK) on WireGuard sockets.
TUNLX_VPN_DEBUG — 1, true or yes logs VPN provider diagnostics (e.g. Mullvad CLI calls and relay-list retries) at info level. Without it they log at debug level, so they only appear when debug logging is on.

MediaFlow

Variable Equivalent in config Purpose
TUNLX_MEDIAFLOW_BASE_URL proxies[].mediaFlowURL Fallback when a proxy doesn't override the URL.
API_PASSWORD — Default API password for MediaFlow requests.
MEDIAFLOW_API_PASSWORD — Fallback if API_PASSWORD is unset.

Tailscale (embedded tsnet)

These take effect at startup. Precedence is config.json → these env vars → defaults (see tailnet_config.go).

Variable Equivalent in config Purpose
TUNLX_TS_ENABLED tailscale.enabled Turn the embedded tsnet node on.
TUNLX_TS_HOSTNAME tailscale.hostname Hostname advertised to the Tailnet (default tunlx).
TUNLX_TS_STATE_DIR tailscale.stateDir Where the node persists state (default: tailscale-state next to the config file).
TUNLX_TS_CONTROL_URL — Custom Headscale / control plane URL.
TUNLX_TS_EPHEMERAL tailscale.ephemeral Register as an ephemeral node.
TS_AUTHKEY tailscale.tsAuthKey Standard Tailscale auth key for first-time login. Takes precedence over TUNLX_TS_AUTHKEY if both are set.
TUNLX_TS_AUTHKEY tailscale.authKey Legacy tunlx-specific auth key, kept for older deployments. Prefer TS_AUTHKEY for new ones.

Install-time (for install.sh --silent)

Variable Purpose
TUNLX_ADMIN_PASSWORD Set the initial admin password non-interactively.
TUNLX_TAILSCALE_ONLY Set to 1 to bind only on the Tailnet.
TUNLX_TAILSCALE_AUTHKEY Auth key the installer passes to the Tailscale node.
TUNLX_TAILSCALE_HOSTNAME Hostname for the Tailscale sidecar.

File locations

Path Purpose
config.json Everything you configure. It is wherever -configFile points: ./config.json when you run the binary without the flag, /app/data/config.json in the container images (the installer and the baseline compose file mount ./data there).
config.json.bak Next to the config file. The configuration as it was before the last bulk change that removes or replaces entries: Delete all categories, an M3U or iptv-org import that replaces a category, or an Import Proxies that overwrites. It is the file as stored, so as sensitive as config.json (stream addresses in it can carry a provider login) and kept private to its owner the same way; do not commit or share it. Copy it over config.json and restart to take that change back. Only the last copy is kept, and the change is refused if the copy cannot be written.
config.key Next to the config file. The key that encrypts the secrets in it; keep the two together and never commit either.
initial-admin-password.txt Next to the config file. The first-run admin password, until you change it.
tailscale-state/ Next to the config file (/app/data/tailscale-state in the container images). The embedded Tailscale node's identity; TUNLX_TS_STATE_DIR or tailscale.stateDir moves it.
/etc/wireguard/wg0.conf The WireGuard config (TUNLX_WG_CONF changes the path). wg0.conf.bak beside it is the config the last save replaced.
examples/config.example.json Canonical sample with every field documented.

Admin HTTP endpoints

A short list of endpoints the dashboard uses — handy for scripting:

Method Path Purpose
GET /metrics Host CPU / memory / network-IO JSON (container health)
GET /config The running configuration (secrets as codes, see below)
POST /config Save a configuration; codes are put back before it is read
POST /config/reveal Exchange codes for the values they stand for
GET /outbound-ip Detected egress IP (VPN-aware)
GET /outbound-ip/non-vpn Egress IP via system route
GET /tailnet/status Embedded Tailscale node state
GET /tailnet/peers Tailnet exit-node candidates
POST /tailnet/action Apply exit-node selection
GET /tailnet/config Tailscale config snapshot
POST /networking/config Set inbound + outbound modes
POST /process/restart In-place re-exec (used after listener changes)
GET /xtreme-service/tests Stream health test status
GET /xtreme-service/tests/history Past stream-test results
GET /integrations/iptv-org/metadata Available countries / categories from iptv-org
POST /integrations/iptv-org/refresh Force-refresh the iptv-org cache
POST /setup/state Mark dashboard onboarding complete

All endpoints except /metrics require an authenticated session (admin cookie) and sit behind the same rate limiters that protect the login and EPG endpoints. /metrics is deliberately unauthenticated so the container health check can reach it — treat the dashboard port as private and keep it firewalled from the public internet.

Secrets in the answers

GET /config, GET /proxies, GET /vpn/wireguard-config?metadata=true and the Xtream service's groups, channels and stream tests never carry a provider password, a viewer's password hash, a token, a WireGuard key or the login inside a stream address. (GET /vpn/wireguard-config without metadata=true still answers with the file as plain text, keys included, for scripts that download it.) Each secret comes as a code: tunlx-secret.v1. and 22 characters for a value, tunlx-secret.v1u. and 22 for one part of an address (/live/alice/tunlx-secret.v1u.…/1001.ts). The same value always gets the same code, also after a restart, and a code is no use without the admin session.

Send a code back where it came from and the server puts the value back before anything reads it, so a script that fetches /config, changes one field and posts it back keeps every secret. To read a value, post the codes to /config/reveal:

curl -s -b cookies.txt -H 'Content-Type: application/json' \
  -d '{"handles":["tunlx-secret.v1.AbCdEfGhIjKlMnOpQrStUv"]}' \
  http://localhost:6060/config/reveal
# {"missing":[],"values":{"tunlx-secret.v1.AbCdEfGhIjKlMnOpQrStUv":"the password"}}

A code the server does not know (the value was changed since it was fetched, or it came from another instance) is refused with 409 and a sentence that says to reload; /config/reveal lists it under missing. Every call to /config/reveal is logged as secrets shown on request, with a count and no values.

Quick "what's running" checklist

# Process is up?
curl -s http://localhost:6060/metrics | head

# What does egress look like right now?
curl -s http://localhost:6060/outbound-ip
curl -s http://localhost:6060/outbound-ip/non-vpn

# Tailscale up?
curl -s http://localhost:6060/tailnet/status

# Container logs (Docker / Compose)
docker logs --tail=200 tunlx