Route macOS traffic through WireGuard tunnels.
Rules you can read.
What it is
A SOCKS5/HTTP proxy that reads a plain-text config, matches each outbound connection against a list of rules, and forwards it through a named WireGuard tunnel, or directly, or drops it.
Not a VPN. The tunnel is yours; woute decides which traffic uses it.
The entire program is a single Python file. Read it before you run it.
Connection flow
Why
I already had WireGuard tunnels. I wanted per-domain rules where the config still made sense six months later, and where the code handling my traffic was short enough to read before running it with sudo.
Most alternatives are unreadable (Clash YAML, sing-box JSON), closed source (Surge), or both. Running any of them with sudo means handing control of your network traffic to code you probably have not read. woute is a few hundred lines of Python. The routing config looks like what it does. Read it first, then run it.
Who it's for
wg-quick.Install
git clone https://github.com/harryngai/woute
cd woute && ./install.sh
The installer checks and installs all dependencies (Homebrew, python3, wireguard-go, wireguard-tools), configures PATH, and copies woute to ~/.local/bin/.
Config
A config file has five section types. Each [Tunnel: name] block registers a named tunnel automatically — no separate proxy declaration needed.
[General]
socks5-listen = 7890
dns = 1.1.1.1, 1.0.0.1
[Log]
level = warning
folder = log
[Tunnel Group]
VPN = Taiwan, Japan
[Tunnel: Taiwan]
…WireGuard keys and endpoint…
[Rule]
*.anthropic.com -> VPN
default -> direct
Rules
Evaluated top-to-bottom. First match wins. The right side names a tunnel, group, or built-in action.
[Rule]
*.anthropic.com -> VPN # through VPN group
*doubleclick* -> block # drop the connection
192.168.0.0/16 -> direct # LAN stays local
default -> direct # catch-all, must be last
| Pattern | Matches |
|---|---|
| *.example.com | example.com and all subdomains |
| example.com | only exactly example.com |
| *keyword* | any domain containing “keyword” |
| 1.2.3.0/24 | IP addresses in CIDR range |
| default | everything not matched above |
| Action | Effect |
|---|---|
| direct | Connect without a tunnel |
| block | Drop the connection |
| name | Route through the tunnel or group named name |
Groups
A group holds multiple tunnels and tries them in order — first tunnel with a recent WireGuard handshake wins. To change priority, reorder members in the config and restart.
[Tunnel Group]
VPN = Taiwan, Japan
Tunnel config
Each WireGuard peer gets a named section. Keys map directly to WireGuard config.
[Tunnel: MyTunnel]
private-key = YOUR_PRIVATE_KEY
address = 10.14.0.2
dns = 1.1.1.1, 1.0.0.1
mtu = 1280
public-key = PEER_PUBLIC_KEY
endpoint = wg.example.com:51820
allowed-ips = 0.0.0.0/0
| Key | What it sets |
|---|---|
| private-key | Your WireGuard private key |
| address | IP assigned to your end of the tunnel; must be unique per tunnel |
| dns | DNS servers used through this tunnel |
| mtu | Maximum packet size; 1280 is safe for most networks |
| public-key | The peer server's public key |
| endpoint | Peer server address and UDP port |
| allowed-ips | Which IPs to route through this tunnel |
Run
sudo woute woute.conf
sudo is required — WireGuard needs to create network interfaces and modify the routing table. In a terminal, a live panel shows tunnel status and recent traffic. Press q to stop. Routes are cleaned up automatically.
| Key | Action |
|---|---|
| l | Toggle log view |
| t | Test rules / test selected log entry |
| r | Reload config, or switch to a new config file |
| q | Quit |
When stdout is not a TTY, woute skips the panel and logs only. Set folder = log in [Log] to write woute-YYYYMMDD.log alongside the config file.
CLI reference
| Command | Description |
|---|---|
| sudo woute | Start with default.conf |
| sudo woute <file> | Start with a specific config file |
| sudo woute <file> -v | Start with verbose logging |
| woute -t <host> | Show which rule matches a given host |