Automatically maintain access to your local network devices across multiple subnets while using Mullvad VPN on macOS.
Mullvad VPN's "Local Network Sharing" feature only works for devices on the same subnet as your Mac. If you have a multi-subnet home network (common with VLANs, separate IoT networks, or segmented server infrastructure), you lose access to devices on other subnets when connected to Mullvad VPN.
This tool solves that problem by automatically adding static routes for your specified local subnets, ensuring continuous access to all your local devices while maintaining VPN security for internet traffic.
- ✅ Automatic route management when Mullvad connects/disconnects
- ✅ Runs silently in the background via LaunchAgent
- ✅ Checks every 60 seconds and restores routes if needed
- ✅ Survives reboots (starts automatically at login)
- ✅ Configurable for any network topology
- ✅ Zero performance impact
- ✅ Works with macOS 13+ (Ventura, Sonoma, Sequoia, Tahoe)
- A background script checks every 60 seconds if Mullvad VPN is connected
- When connected, it adds static routes for your configured local subnets
- Traffic to your local subnets bypasses the VPN tunnel and goes through your local gateway
- All other traffic (internet) continues through the Mullvad VPN tunnel
- Routes are automatically removed when Mullvad disconnects
- macOS 13.0 or later
- Mullvad VPN app installed
- Multiple subnets in your local network
- Basic terminal knowledge
Clone this repository or download these two files:
mullvad-local-routes.shcom.mullvad.local-routes.plist
Edit mullvad-local-routes.sh and update these variables for your network:
# Your local gateway IP (usually your router/firewall)
LOCAL_GATEWAY="192.168.1.1"
# Your local subnets
LOCAL_SUBNETS=(
"192.168.10.0/24" # Example: Main LAN
"192.168.20.0/24" # Example: Server subnet
"192.168.30.0/24" # Example: IoT subnet
)Finding your gateway: Run netstat -rn | grep default and look for your gateway IP.
Finding your subnets: Run netstat -rn and look for your local network ranges.
# Copy script to system location
sudo cp mullvad-local-routes.sh /usr/local/bin/
# Make it executable
sudo chmod 755 /usr/local/bin/mullvad-local-routes.sh
# Test it manually
sudo /usr/local/bin/mullvad-local-routes.shThe script needs to run route commands without password prompts.
# Edit sudoers file
sudo visudoAdd this line at the bottom (replace YOUR_USERNAME with your actual username):
YOUR_USERNAME ALL=(ALL) NOPASSWD: /sbin/route
To find your username: Run whoami
In visudo:
- Press
oto create a new line - Type the line above
- Press
Esc - Type
:wqand press Enter
# Copy LaunchAgent to your user LaunchAgents directory
cp com.mullvad.local-routes.plist ~/Library/LaunchAgents/
# Load the LaunchAgent
launchctl load ~/Library/LaunchAgents/com.mullvad.local-routes.plist
# Verify it's loaded
launchctl list | grep mullvadYou should see local.mullvad.routes in the output.
# Connect to Mullvad VPN first
# Then run the script
sudo /usr/local/bin/mullvad-local-routes.sh
# Check if routes were added
netstat -rn | grep "192.168"You should see routes for your configured subnets pointing to your LOCAL_GATEWAY.
# Delete a route (replace with your actual subnet)
sudo route delete -net 192.168.20.0/24
# Verify it's gone
netstat -rn | grep "192.168.20"
# Wait 65 seconds
sleep 65
# Check if it came back automatically
netstat -rn | grep "192.168.20"The route should automatically reappear!
# Ping a device on another subnet (replace with your device IP)
ping 192.168.20.10You should be able to reach devices on all configured subnets.
Check LaunchAgent status:
launchctl list | grep mullvad
cat /tmp/mullvad-routes.error.logCommon issues:
- Script permissions: Run
ls -l /usr/local/bin/mullvad-local-routes.sh- should show-rwxr-xr-x - Sudo configuration: Test with
sudo route -n get default- should run without password prompt - LaunchAgent not loaded: Reload with
launchctl unloadthenlaunchctl load
The script only maintains routes while Mullvad VPN is connected. When you disconnect from Mullvad, the routes are removed (this is by design - you don't need them without VPN).
Check your Mullvad interface name:
ifconfig | grep utunUpdate the VPN_INTERFACE variable in the script if different.
- Verify Mullvad is connected:
ifconfig utun6 - Verify routes exist:
netstat -rn | grep "192.168" - Check firewall rules on your router/firewall
- Verify "Local Network Sharing" is enabled in Mullvad app
# Unload LaunchAgent
launchctl unload ~/Library/LaunchAgents/com.mullvad.local-routes.plist
# Remove files
rm ~/Library/LaunchAgents/com.mullvad.local-routes.plist
sudo rm /usr/local/bin/mullvad-local-routes.sh
# Remove sudoers rule
sudo visudo
# Delete the line you added, save and exitInternet
↓
Mullvad VPN (on Mac)
↓
Router/Firewall (192.168.1.1)
├─ Main Network: 192.168.1.0/24 (Mac is here)
├─ Server Network: 192.168.10.0/24 (NAS, servers)
└─ IoT Network: 192.168.20.0/24 (smart home devices)
Configuration:
LOCAL_GATEWAY="192.168.1.1"
LOCAL_SUBNETS=(
"192.168.10.0/24"
"192.168.20.0/24"
)Internet
↓
Mullvad VPN (on Mac)
↓
OPNsense Firewall (10.0.1.1)
├─ VLAN 10 (Workstations): 10.0.10.0/24 (Mac is here)
├─ VLAN 20 (Servers): 10.0.20.0/24
├─ VLAN 30 (Storage): 10.0.30.0/24
└─ VLAN 40 (Management): 10.0.40.0/24
Configuration:
LOCAL_GATEWAY="10.0.1.1"
LOCAL_SUBNETS=(
"10.0.20.0/24"
"10.0.30.0/24"
"10.0.40.0/24"
)When Mullvad VPN connects, it creates a utun interface and adds a default route that captures all traffic. This is the "full tunnel" mode that routes everything through the VPN.
Static routes with more specific destinations (like 192.168.20.0/24) take precedence over the default route (0.0.0.0/0), allowing us to selectively bypass the VPN for local traffic.
Mullvad's macOS app has split tunneling, but it works at the application level (excluding specific apps), not at the network level (excluding specific subnets). You'd have to exclude every app that needs local network access, which is impractical.
- ✅ Internet traffic still goes through Mullvad VPN
- ✅ DNS queries still go through Mullvad (unless you've configured local DNS)
- ✅ Only explicitly configured local subnets bypass the VPN
⚠️ Devices on your local subnets can see your real IP address⚠️ Local network traffic is not encrypted by the VPN
This is the same behavior as Mullvad's "Local Network Sharing" - it just extends it to multiple subnets.
- CPU Usage: Negligible (~0.1 seconds per check, once per minute)
- Memory Usage: <1 MB
- Network Impact: None (only modifies routing table)
- Battery Impact: None (script sleeps between checks)
| macOS Version | Status |
|---|---|
| macOS 13 (Ventura) | ✅ Tested |
| macOS 14 (Sonoma) | ✅ Tested |
| macOS 15 (Sequoia) | ✅ Tested |
| macOS 26 (Tahoe) | ✅ Tested |
Note: macOS 13+ is required for the script syntax. Earlier versions may work with modifications.
Contributions welcome! Please open an issue or pull request.
MIT License - see LICENSE file for details
- Inspired by the limitations of Mullvad VPN's single-subnet "Local Network Sharing"
- Built on macOS's standard
launchdand routing capabilities
If you find this useful, please ⭐ star this repo!
For issues or questions, please open a GitHub issue.
Disclaimer: This tool modifies your system's routing table. While it's designed to be safe, use at your own risk. Always test in a non-production environment first.