Please take all of this advice carefully and double check recommendations for yourself. I have no particular networking knowledge and have cobbled this approach together from dozens of websites and online posts over the past few years (along with the recent help of AI). This is intended only for MacOS. If anybody has actual networking knowledge and wants to help refine this, please do! Please point out any errors or improvements.
First, install WireGuard. You can use either the GUI app (which is only available through the Mac App Store, and so requires an Apple Account), or you can use the CLI (wireguard-go and wireguard-tools, available through Homebrew). I used the CLI for the past 4-5 years but have recently switched to the GUI app because Iâm trying to move away from using Homebrew (for reasons irrelevant to this guide). Install the version of the software you prefer. Go to your VPN provider and generate WireGuard configs. I use both ProtonVPN and Mullvad. Set up the configs in your WireGuard app following the guides offered by your provider: either import them into the GUI app, or place them in the relevant folder for the CLI and use âwg-quick up confg_name.confâ to enable.
If using the GUI app, ensure to tick the âOn Demandâ boxes for Ethernet and Wi-Fi when editing the configurations. Whichever configuration you want to use when the system starts, make sure âConnect on Demandâ is enabled under MacOSâs settings (VPN â click the âiâ button).
You should also set WireGuard to load on system boot. This is easier with the GUI app, since you just need to add it to Login Items. With the CLI youâll need to use a startup script.
- Create the kill switch:
When WireGuard is up and running and connected, find the interface that itâs using by running this command in terminal:
ifconfig
For me, itâs utun4. Iâve read that the interface MacOS assigns to the WireGuard tunnel can change, but I havenât noticed this happening despite many restarts and updates over many months. I can determine itâs utun4 for me because, scanning the output of the ifconfig command, utun4 shows something like âinet XX.XX.XX.XX â XX.XX.XX.XXâ where the XX.XX.XX.XX IP matches that listed in the WireGuard configuration fileâs âAddressâ field under [Interface].
Next, create the kill switch file:
sudo nano /etc/killswitch.pf.conf
Paste the following. Replace âutunXâ with the interface WireGuard is using, e.g. utun4. Replace the VPN server IPs and ports with the actual IPs and ports from the configs youâve generated, which are included in the config files. If youâre using only one config, then just include that; if youâre using many, include them all. Including them here will allow WireGuard to bootstrap and connect.
# Block all traffic by default
block all
# Allow local network (en0)
pass on en0 from any to 192.168.0.0/24
pass on en0 from 192.168.0.0/24 to any
# Allow loopback traffic
pass on lo0 inet from 127.0.0.1 to 127.0.0.1
pass on lo0 inet6 from ::1 to ::1
# Allow DHCP for IP address negotiation
pass out proto udp from any port 68 to any port 67 keep state
pass in proto udp from any port 67 to any port 68 keep state
# Allow traffic ONLY through the WireGuard tunnel interface
pass on utunX all
# Allow traffic to your VPN server endpoints
pass out proto udp from any to XX.XX.XX.XX port 51820 keep state
pass out proto udp from any to XX.XX.XX.XX port 51820 keep state
pass out proto udp from any to XX.XX.XX.XX port 51820 keep state
# Allow mDNS if needed
pass out proto udp from any to 224.0.0.251 port 5353 keep state
# HTTPS and NTP through tunnel only
pass out on utun4 proto tcp to any port 443 keep state
pass out on utun4 proto udp to any port 123 keep state
pass in proto udp from any port 123 to any keep state
# Block uPNP unless needed
block out proto udp to 224.0.0.0/4 port 1900
# Block all IPv6 unless needed
block quick inet6 all
Regarding local network traffic (en0), find your subnet with
ifconfig en0 | grep "inet "
This gives me âinet 192.168.0.104 netmask 0xffffff00 broadcast 192.168.0.255â, for example, so my subnet is 192.168.0.0/24. Replace that given in the config with your own subnet.
Next, integrate killswitch.pf.conf into the system PF config:
sudo nano /etc/pf.conf
Add this line at the bottom:
include "/etc/killswitch.pf.conf"
Test the rules:
sudo pfctl -nf /etc/pf.conf # Syntax check
sudo pfctl -Fa -f /etc/pf.conf # Load rules
sudo pfctl -e # Enable PF
Verify the loaded rules:
sudo pfctl -sr
You should see your rules listed.
Make the kill switch persistent across reboots: turn on MacOS Firewall in System Settings - Network - Firewall - Turn On.
You can confirm that the kill switch is working by disconnecting from WireGuard and trying to access anything on the internet. All connections should fail.
- (Optional) Integrate NextDNS
If you want to use NextDNS, then this is how Iâve managed to get it to work. Install NextDNS CLI following the instructions at my.nextdns.io/XXXXX/setup where XXXXX is the ID of the profile you want to use. Once NextDNS is installed, activated, and started on your system, you can edit your WireGuard configs to tell WireGuard to use NextDNS. In the GUI, simply hit Manage Tunnels, and Edit. Under [Interface], change DNS to:
DNS = 127.0.0.1, ::1
Do this for all of your WireGuard configs. If youâre using WireGuard CLI, then edit your files in terminal. You can then edit your killswitch config (sudo nano /etc/killswitch.pf.conf) to include the following. You can add it anywhere in the file:
# DNS through NextDNS only
block out proto udp from any to any port 53
block out proto tcp from any to any port 53
pass out proto udp from any to 127.0.0.1 port 53 keep state
pass out proto tcp from any to 127.0.0.1 port 53 keep state
This forces all DNS queries to 127.0.0.1:53, where NextDNS should be listening.
If at any point youâve inadvertently or unexplainably lost internet connectivity during the setup and want to disable the killswitch, simply use
sudo pfctl -d
I would personally make sure youâre comfortable with NextDNS CLIâs commands (sudo nextdns start/stop/restart/activate/deactivate/install/uninstall/etc.) and with what each command does before proceeding with this step of the set up. If youâre using WireGuard via CLI as well, please make sure youâre comfortable with its most common commands (sudo wg-quick up XXX.conf, wg-quick down XXX.conf, wg-quick status, etc.). Also familiarise yourself with the commands to control the PF firewall:
sudo pfctl -e # Enable
sudo pfctl -d # Disable
sudo pfctl -nf /etc/killswitch.pf.conf # Verify syntax
sudo pfctl -f /etc/pf.conf # Load rules
sudo pfctl -sr # Confirm rules loaded
If people are interested in my set up using LittleSnitch, where I have two profiles (one called Connect, which only allows connections to my WireGuard VPN endpoints and NextDNS, and one called Established, which contains all other rules) I can briefly describe that too. This really isnât necessary, though.
Last edited by @JG 2026-01-10T19:28:29Z