Skip to content

Inbound & Outbound Modes

tunlx separates how it listens from how it dials out. The two are configured independently, and either can be flipped from the dashboard without editing config.json by hand.

The global Networking dialog

flowchart LR
    classDef in   fill:#22d3ee,stroke:#0e7490,color:#0f172a
    classDef out  fill:#a855f7,stroke:#7e22ce,color:#fff
    classDef core fill:#6366f1,stroke:#4338ca,color:#fff

    subgraph INBOUND["Inbound (listeners)"]
      I1["public"]:::in
      I2["tailscale"]:::in
      I3["both"]:::in
    end

    subgraph OUTBOUND["Outbound (egress)"]
      O1["public"]:::out
      O2["vpn (WireGuard)"]:::out
      O3["tsexit (Tailscale exit node)"]:::out
    end

    Core["tunlx core<br/>proxies + services"]:::core

    INBOUND --> Core --> OUTBOUND

Inbound modes

Which networks can reach your dashboard and proxy listeners?

Mode Behavior
public Listeners bind on the host's public interface(s). Default.
tailscale Listeners bind only on the embedded Tailscale node. No public exposure.
both Bind public and Tailscale interfaces. Useful for staged rollouts.

Switching modes rewrites bind addresses, so tunlx triggers an in-place process restart to re-bind cleanly. Active stream sessions reconnect on their own — clients re-dial as part of normal Xtream client behavior.

Outbound modes

How does egress leave the host?

Mode Behavior
public Use the system routing table. Whatever IP your host has, that's the source.
vpn Egress sockets bind to the WireGuard interface (SO_BINDTODEVICE). See WireGuard / VPN.
tsexit Egress is routed through the configured Tailscale exit node. See Tailscale Access.

Per-proxy override beats global

Global outbound is the default for every proxy. Individual proxies and Xtream services can still set their own route (system, vpn, tsexit, direct) to override. See Feature Tour → Per-proxy egress.

Fail-closed behavior

By default, if a proxy requests vpn or tsexit egress and that interface is down or unavailable, tunlx will silently fail open and use the public system route. To prevent IP leaks, turn on Block traffic if the VPN / Tailscale path is down in the Networking dialog (or set failClosed: true in the networking block), or set strictEgress: true on individual proxies, to force a 503 error instead.

Configure from the dashboard

The dashboard exposes both modes in the same panel:

  1. Open Networking (Advanced mode).
  2. Pick an Inbound mode.
  3. Pick an Outbound mode. If you choose tsexit, the Exit Node selector appears with the list of advertised Tailnet exit-node peers.
  4. Optionally switch on Block traffic if the VPN / Tailscale path is down so proxies answer 503 instead of falling back to your normal connection (see the note above).
  5. Save. tunlx applies the change and, if necessary, restarts the process in place.

Configure in config.json

{
  "networking": {
    "inbound": "tailscale",
    "outbound": "vpn"
  }
}

If networking is absent, both modes default to public.

Picking exit nodes from the Tailnet

When outbound is tsexit, tunlx queries the local Tailscale node for the list of peers that advertise themselves as exit nodes and exposes them in the dashboard. Pick by hostname instead of memorizing Tailnet IDs.

Under the hood, the dashboard calls:

Method Path Purpose
GET /tailnet/status Current Tailscale state (logged-in, IPs, etc.)
GET /tailnet/peers Advertised exit nodes available to this node
POST /tailnet/action Apply exit-node selection
POST /networking/config Persist inbound + outbound choice

You can script the same flow from cron / CI if you need to flip modes during maintenance windows.

Reachable URLs

When inbound is tailscale, the dashboard surfaces the Tailnet-reachable URL (e.g. http://tunlx.example-tailnet.ts.net:6060) so you don't have to guess what hostname to give other operators. Logic lives in inbound_endpoints.go.

The public IP shown in the navbar

To show the server's outward-facing address, tunlx asks an outside service for it: api.ipify.org, then checkip.amazonaws.com, ifconfig.me and ip-api.com if the earlier ones do not answer. The request goes out the way the dashboard's own traffic does (through WireGuard when it is up, for the VPN address; directly for the ISP address). Whatever the number of open dashboards, the answer is kept for a minute, so that is roughly two lookups a minute while a dashboard is open (a few more while the services are unreachable), and none otherwise.

Set TUNLX_DISABLE_EXTERNAL_IP_LOOKUP=true to turn the lookups off. The navbar then shows no address, and the wizards no longer prefill one; nothing else depends on it.

Avoid double-hopping

If you set per-proxy route: vpn and global outbound tsexit, you've asked for traffic to go through WireGuard and through a Tailscale exit node. That may be what you want — but check, because the latency and debuggability cost is real.

A safe rule of thumb:

  • One policy per workload. Use either per-proxy egress or global outbound, not both, for the same proxy.