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.

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.
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