Zscaler Split Tunnel is a macOS utility for keeping Zscaler active only where you need it. It removes Zscaler's broad catch-all routes, restores explicit routes for corporate resources, and lets the rest of your traffic use the normal network path.
The project includes two operating modes:
| Mode | Best for | Interface |
|---|---|---|
| Native macOS app | Day-to-day use, status checks, route editing, and helper operations | SwiftUI menu bar app with a privileged helper |
| Shell daemon | Headless or scriptable environments | Bash daemon with launchd autostart support |
This is an independent utility and is not affiliated with Zscaler. It changes macOS routes and may interrupt network access if misconfigured. Review your routes before using it on a production laptop.
- Removes Zscaler's broad IPv4 routes that capture most internet traffic.
- Restores only the configured domains, IPs, and CIDR ranges through Zscaler.
- Supports direct overrides for destinations that must bypass Zscaler.
- Monitors network and configuration changes, then refreshes routes automatically.
- Resolves domains to IP addresses with local caching.
- Provides a SwiftUI menu bar app with live status, route counters, and controls.
- Includes a scriptable daemon for teams that prefer CLI-first operation.
- Supports optional office WiFi routing for direct overrides when a corporate Ethernet network is detected.
Clone the repository and install the shell daemon:
git clone https://github.com/balcsida/zscaler-split-tunnel.git
cd zscaler-split-tunnel
chmod +x zscaler-split-tunnel.sh
sudo mkdir -p /usr/local/bin
sudo cp zscaler-split-tunnel.sh /usr/local/bin/zscaler-split-tunnel
sudo chmod +x /usr/local/bin/zscaler-split-tunnel
sudo zscaler-split-tunnel --startCheck the current state without sudo:
zscaler-split-tunnel --status
zscaler-split-tunnel --listThe native app lives in macOS/ and pairs a SwiftUI menu bar interface with a
privileged helper installed through SMJobBless. It is intended for interactive
use: start or stop monitoring, refresh routes, manage Zscaler, flush DNS, and
edit both route lists from a settings window.
Build from source with Xcode 16+:
cd macOS
xcodegen generate
open ZscalerSplitTunnel.xcodeprojBuild both targets:
ZscalerSplitTunnelcom.zscaler-split-tunnel.helper
The app runs as a menu bar item. The helper performs privileged route operations through XPC; the app itself stays unprivileged.
# Start split tunneling and start Zscaler if needed
sudo zscaler-split-tunnel --start
# Stop the split tunnel daemon
sudo zscaler-split-tunnel --stop
# Stop Zscaler app and services
sudo zscaler-split-tunnel --kill
# Run the daemon in the foreground while debugging
sudo zscaler-split-tunnel --daemon --verboseZscaler routes are destinations that should continue to use the Zscaler tunnel.
zscaler-split-tunnel --add internal.company.com
zscaler-split-tunnel --add 192.168.1.0/24
zscaler-split-tunnel --add 10.0.0.5
zscaler-split-tunnel --remove internal.company.com
zscaler-split-tunnel --show-config
zscaler-split-tunnel --clear-configDirect overrides are destinations that should use the normal gateway even when Zscaler has installed a more-specific route for them.
zscaler-split-tunnel --add-bypass api.example.com
zscaler-split-tunnel --remove-bypass api.example.com
zscaler-split-tunnel --show-bypass
zscaler-split-tunnel --clear-bypassInstall or remove the launchd service:
sudo zscaler-split-tunnel --enable-autostart
sudo zscaler-split-tunnel --disable-autostartTeam defaults are versioned in this repository:
| File | Purpose |
|---|---|
config/zscaler-split-tunnel.conf |
Default destinations routed through Zscaler |
config/zscaler-bypass.conf |
Default direct overrides routed outside Zscaler |
User-specific files live under ~/.config/zscaler-split-tunnel/:
| File | Purpose |
|---|---|
routes.conf |
User Zscaler routes |
bypass.conf |
User direct overrides |
office-mode.json |
Optional office WiFi routing settings |
domain-cache.txt |
Runtime domain resolution cache |
remote-route-cache.txt |
Runtime cache for remote route lists |
Example routes.conf:
# Domains, IPs, or CIDR ranges routed through Zscaler
internal.company.com
vpn.company.com
192.168.100.0/24
10.0.0.50
The --add, --remove, --add-bypass, and --remove-bypass commands update
the user files. Defaults remain untouched, and a running daemon reloads changes
automatically.
Office mode can route direct overrides through a guest WiFi gateway when the Mac is connected to a corporate Ethernet network. The detector uses CDP/LLDP discovery packets and an optional switch-name allow list.
Create ~/.config/zscaler-split-tunnel/office-mode.json:
{
"enabled": true,
"targetSSID": "Guest_WiFi",
"cdpGracePeriodSeconds": 120,
"switchNamePatterns": []
}Leave switchNamePatterns empty to accept any detected switch while office mode
is enabled.
- Zscaler installs broad routes such as
1.0.0.0/8,2.0.0.0/7, and128.0.0.0/2that can capture most IPv4 traffic. - The tool removes those broad routes.
- It resolves configured domains, validates IPs and CIDR ranges, and adds
explicit routes for Zscaler destinations through the active
utuninterface. - It applies direct overrides through the normal gateway, or through the office WiFi gateway when office mode is active.
- A monitor loop refreshes route state on a low-frequency interval and reacts to configuration or network changes.
| Path | Description |
|---|---|
/tmp/zscaler-split-tunnel.log |
Shell daemon log |
/tmp/zscaler-split-tunnel.pid |
Shell daemon PID file |
/Library/LaunchDaemons/com.github.zscaler-split-tunnel.plist |
Optional launchd service |
~/.config/zscaler-split-tunnel/ |
User configuration and runtime caches |
Inspect the current state:
zscaler-split-tunnel --status
zscaler-split-tunnel --listFollow daemon logs:
tail -f /tmp/zscaler-split-tunnel.logVerify where a destination is routed:
route get "$(dig +short example.com | head -1)"If a destination is not loading:
- Confirm DNS resolution with
dig +short yourdomain.com. - Check the selected route with
route get <ip-address>. - Inspect the route table with
netstat -rn. - Test connectivity with
curl -I https://yourdomain.com. - Review recent helper or daemon logs before changing the route list.
- macOS 14 or newer for the native app
- Zscaler Client Connector installed
- Administrator privileges for route changes
- Standard macOS command line tools:
route,netstat,dig, andcurl - Xcode 16+ and XcodeGen for the native app
- The privileged helper and shell daemon execute
/sbin/route; keep route input limited to trusted domains, IP addresses, and CIDR ranges. - Do not commit personal routes, credentials, private hostnames, or internal URLs.
- Test route changes on a non-critical network before deploying broadly.
- Use the status and route inspection commands before and after changing config.
MIT
