WireGuard / VPN¶
tunlx ships a first-class WireGuard integration. Bring your own config, or let tunlx fetch and rotate Mullvad configurations on a schedule.
Two modes¶
| Mode | Use when… |
|---|---|
manual |
You already have a WireGuard config (e.g. corporate VPN, private peer). |
provider |
You want tunlx to fetch and rotate configs from a supported provider. |
Currently the only supported provider is Mullvad.
Manual mode¶
What this gives you:
- The WireGuard interface is brought up at start (or by the systemd unit
when
autoStartOnBootistrue). - Per-proxy
route: vpnbinds outbound sockets to the interface (SO_BINDTODEVICE), so only proxies that opt in are tunneled. - Set
TUNLX_WG_MARKif you need a firewall mark (SO_MARK) for policy routing.

The configuration box shows dots in place of the private key and any preshared key. Show keys puts them in until you close the dialog, Copy copies the real file either way, and Save & restart with the dots untouched keeps the keys the file has. To change a key, press Show keys first.
Stopping or restarting the tunnel from the dashboard asks first, because every
proxy on the vpn route fails until it is back up, and so does anything you
reach tunlx through if you reach it over that tunnel.
The previous config is kept
Whenever tunlx replaces the WireGuard config (Save & restart, Use
on a server from the provider lookup, or the scheduled refresh), the config
it replaces is copied to wg0.conf.bak next to it first. Only the last one
is kept, so copy it somewhere safe before applying a third.
Provider mode (Mullvad)¶
{
"vpn": {
"mode": "provider",
"autoStartOnBoot": true,
"provider": {
"name": "mullvad",
"refreshIntervalMinutes": 15,
"mullvad": {
"accountNumber": "1234567890123456",
"country": "us",
"city": "",
"hostname": "",
"ownedOnly": false
}
}
}
}
A background scheduler (vpn_scheduler.go):
- Authenticates against Mullvad with your account number.
- Picks a server matching your filters (
country,city,hostname,ownedOnly). - Rewrites the WireGuard config and brings the interface up.
- Refreshes on the configured interval (default
15minutes). - Persists the last selected server snapshot back to
config.jsonso restarts pick up where they left off.
Filter precedence
hostname is strongest, then city, then country. Changing
country clears any stale city / hostname to avoid picking a
server that no longer matches.
Provider mode needs the Mullvad CLI
The server list comes from the Mullvad command-line tool
(mullvad relay list), so the Mullvad app, which provides the mullvad
command and the daemon behind it, has to be installed on the host. The
tunlx image does not contain it. With the repository's
docker-compose.yaml, uncomment the two Mullvad lines under volumes:
to share the host's CLI and its socket with the container (see
what the baseline compose file grants).
Without them a server lookup fails with "mullvad CLI binary not found".
Per-proxy opt-in¶
Even with VPN configured, individual proxies decide whether to use it:
The direct mode (with sourceIP) is the inverse: bind to a specific
local IP so policy routing can deliberately bypass the VPN for that
proxy. Useful when one upstream requires your real IP.
Containerized routing¶
In Docker / Compose, WireGuard inside the container needs:
services:
tunlx:
cap_add:
- NET_ADMIN
- NET_RAW
devices:
- /dev/net/tun
volumes:
- /etc/wireguard:/etc/wireguard
If you'd rather isolate the WireGuard stack, run a sidecar container
(e.g. gluetun) and put tunlx in its
network namespace. There's a ready-made Compose variant under
deployments/docker/.
Verification¶
# Inside the container or host
wg show
# Confirm the VPN egress IP differs from the host IP
docker exec tunlx curl -s https://api.ipify.org
# tunlx surfaces both IPs through admin endpoints:
curl -s http://localhost:6060/outbound-ip # VPN-aware view
curl -s http://localhost:6060/outbound-ip/non-vpn # System-route view
The dashboard shows both side-by-side in Activity & Telemetry.
Common pitfalls¶
- No
NET_ADMIN/NET_RAW. Interface stays down;wg showshows nothing inside the container. - Missing
/dev/net/tun. WireGuard userspace fallback won't kick in on every host. - Double-hop confusion. Setting global outbound
tsexitand per-proxyroute: vpnsends traffic through both. Pick one per proxy. - MTU. Providers can advertise small MTUs; if HLS segments stall but ping works, suspect MTU first.
Related¶
- Inbound & Outbound Modes
- Tailscale Access — exit-node based egress, no WireGuard
- Configuration Reference → VPN
- Troubleshooting