macOS Sequoia and VPN: How to Set Up a System Kill Switch and Configure Network Extension
Step-by-step guide to setting up a system-level kill switch for VPN on macOS Sequoia and the basics of the Network Extension API. In 60–120 minutes, you'll install the client, import the config, set up the PF filter, automate it, and verify there are no leaks.
Content of the article
- Introduction
- Preparation
- Basic concepts
- Step 1: choose protocol and client
- Step 2: import configuration and initial connection
- Step 3: enable dns leak protection and on-demand
- Step 4: system kill switch via pf: basic setup
- Step 5: automate kill switch: script and launchagent
- Step 6: fine-tuning rules & scenarios for different protocols
- Step 7: advanced section: network extension api basics for kill switch
- Step 8: comprehensive testing: traffic, dns, disconnects
- Result verification
- Common issues and fixes
- Additional features
- Faq
- Conclusion
Introduction
In this step-by-step guide, you'll set up a reliable system-level kill switch for VPN on macOS Sequoia and understand how key parts of the Network Extension API work. We'll start from choosing the protocol and client and go all the way to creating system-level rules using the PF filter and, if you want, dive into advanced integration via Network Extension. By the end, you'll have a predictable connection with no traffic leaks outside the VPN—even during unexpected disconnects. This guide is designed for beginners but includes advanced sections for those who want a deeper understanding.
After completing all steps, you'll be able to: configure a VPN client on macOS Sequoia, import a pre-made configuration file, enable DNS leak protection, create and test a system kill switch using PF, automate enabling and disabling the filter, and optionally try a minimal example with the Network Extension API.
This guide is ideal for: Mac users on macOS Sequoia (current as of 2026), security beginners, developers who want to grasp the principles, and anyone needing guaranteed traffic blocking outside the VPN.
What you should know beforehand: basic experience with System Preferences, the ability to copy commands into Terminal, and understanding what an IP address and port are. That's all. We'll explain API details clearly and to the point.
Time required: basic VPN and kill switch setup takes 60–90 minutes, plus 30 minutes for automation and testing. Set aside an additional 60 minutes for the advanced Network Extension section if you have a developer account.
Preparation
Before starting, make sure you have everything ready. We'll prepare the system, pick the client and config, verify the macOS version, and create a restore point for critical network settings.
Required Tools and Access
- A Mac running macOS Sequoia (version 15.x or newer).
- Administrator rights on your device (needed for configuring PF and installing clients).
- Configuration file for your chosen protocol (WireGuard, OpenVPN, or IKEv2). This might be a .conf for WireGuard, .ovpn for OpenVPN, or IKEv2 parameters.
- VPN server IP address and port. It's best to have the IP, not just a domain name, to avoid dependency on external DNS when establishing the connection.
- Active internet access during setup.
System Requirements
- macOS Sequoia 15.x (check: Apple menu → About This Mac → Overview).
- At least 200 MB free disk space for clients and logs.
- Outgoing connections to your VPN server allowed (ports depend on protocol: WireGuard usually UDP 51820; OpenVPN by default UDP 1194 or TCP 443; IKEv2 UDP 500 and UDP 4500).
What to Download and Install
- WireGuard for macOS or alternatively Tunnelblick/Viscosity for OpenVPN; if using IKEv2, the built-in macOS client is sufficient.
- A text editor for editing configs (for example, built-in TextEdit or any other).
Backup
Network changes affect system settings. Before starting, note your current state. Save your current DNS servers, active interfaces, and default route.
- Open Terminal.
- Run scutil --dns. Copy the main DNS section into a text file.
- Run ifconfig and route -n get default. Save the outputs for reference.
- Create a Time Machine restore point if you use Time Machine.
Tip: Save your VPN server’s IP and port in a note. You'll need these for PF rules to allow outgoing traffic strictly to the VPN server when all else is blocked.
Basic Concepts
To make following the instructions easier, let’s briefly explain key terms using simple language without unnecessary theory.
- VPN — an encrypted tunnel between your computer and a remote VPN server. All your traffic can go through this tunnel.
- Kill switch — a mechanism that blocks all network traffic if the VPN disconnects for any reason. It's leak protection.
- PF (Packet Filter) — macOS's system packet filter. Lets you define rules about what connections are allowed or blocked, irrespective of apps.
- utun interface — a virtual network interface created by the VPN client. For example, utun0. All VPN packets after encryption go through this interface.
- Network Extension API — Apple's framework for developing VPN clients, content filters, and proxies. Allows programmatic control over the tunnel and network settings.
- On-Demand — a set of rules defining when and how the VPN connects automatically (e.g., on any network except trusted ones) and how it behaves when the connection drops.
- DNS leak — when DNS requests go outside the VPN directly to the internet, exposing your domain lookups and possibly your IP.
Tip: If you see unfamiliar terms later, come back here. Knowing what utun and PF mean will make kill switch setup much smoother.
Step 1: Choose Protocol and Client
Goal
Decide on a protocol and client for macOS Sequoia to proceed with configuring the connection and system-level kill switch.
Detailed Instructions
- Consider your use case: want the fastest and simplest? Pick WireGuard. Need compatibility with older networks? Check out OpenVPN. Want native integration without third-party clients? Use IKEv2.
- If choosing WireGuard: install the WireGuard app for macOS from a trusted source and prepare the .conf configuration file.
- If choosing OpenVPN: install Tunnelblick or Viscosity and prepare the .ovpn file.
- If choosing IKEv2: use the built-in macOS client. Prepare parameters: server address (IP), remote ID, local ID, authentication (username/password or certificate), and enable the "Send all traffic" option.
- Get the server IP and port ready: WireGuard usually uses UDP 51820; for OpenVPN check the port in your .ovpn file; IKEv2 uses UDP 500 and 4500.
⚠️ Attention: For a correct system kill switch, you need the server’s exact IP, not just the domain. PF rules will allow connections only to that IP and port. If you have only a domain, resolve it to IP beforehand and note it down.
Tip: For WireGuard, try to use a config where AllowedIPs are set to 0.0.0.0/0, ::/0. This tells the client to route all traffic through the tunnel, simplifying logic and reducing leak risk.
Expected Result
You’ve chosen a protocol, installed the client (or decided to use the built-in one), and have a working config with the server’s IP and known ports.
Possible Issues and Solutions
- Don't know what to choose. Solution: WireGuard is the best start for macOS Sequoia—easy setup, fast, minimal overhead.
- Only have a domain, no IP. Solution: run dig +short your.domain or nslookup your.domain in Terminal and note the IP.
- ISP blocks UDP. Solution: Use OpenVPN with TCP 443 or configured IKEv2, or enable obfuscation if your server supports it.
✅ Check: You have: selected protocol, installed client (or ready built-in IKEv2), configuration, and server IP with ports.
Step 2: Import Configuration and Initial Connection
Goal
Import the configuration into your chosen client and confirm the VPN connects and routes traffic.
Detailed WireGuard Instructions
- Open WireGuard on your Mac.
- Click “Import Tunnel” or “Add Tunnel” and select the .conf file.
- Verify the Address field has VPN subnet addresses (e.g., 10.14.0.2/32), PrivateKey contains your key, and Peer has the Endpoint as IP:port with AllowedIPs = 0.0.0.0/0, ::/0.
- Toggle the switch to connect. The indicator should turn green, showing traffic stats.
Detailed OpenVPN (Tunnelblick) Instructions
- Launch Tunnelblick.
- Drag the .ovpn file onto the Tunnelblick icon in the menu bar or select “Add Configuration” via the app menu.
- Choose to install either “Only for this user” or “For all users,” as needed.
- Connect by pressing “Connect” next to the profile. Enter username and password if prompted.
Detailed IKEv2 (Built-in Client) Instructions
- Go to “System Preferences” → “Network” → “VPN” → “Add VPN Configuration.”
- Select type “IKEv2.”
- Enter the server’s IP as the “Server” address.
- Fill in “Remote ID” (usually the server domain) and “Local ID” if required.
- Choose authentication method: “Username and Password” or “Certificate.” Fill in credentials.
- Enable “Send all traffic” (or similar default route setting).
- Save and click “Connect.”
Tip: If you have access to your VPN provider’s dashboard, it’s easier to use pre-made configs. For example, vpn.how offers ready-to-use configs for WireGuard, OpenVPN, and IKEv2 that you can download and import on macOS. Notably, they provide a personal (not shared) VPN server with a dedicated IP, support for WireGuard, OpenVPN, IKEv2, L2TP, SSTP, servers in Moscow, St. Petersburg, Amsterdam, Frankfurt, London, New York, San Jose, Chicago, Singapore, Sydney, Madrid, Helsinki, Stockholm, Warsaw, Copenhagen, Stavanger, accept Russian payment cards (Tinkoff, Ozon), SBP, USDT/BTC, rates from 490 ₽ per day and 2490 ₽ per month, discounts for long-term use, auto server startup within 5 minutes and no logs. Mentioned once here to keep instructions clear.
Expected Result
VPN connects successfully, you have internet access through the tunnel, and basic parameters match the config.
Possible Issues and Solutions
- Connection fails. Cause: wrong port or IP. Solution: double-check the Endpoint in the config and verify the port with your provider.
- Connected but no internet. Cause: incorrect routes or DNS. Solution: check AllowedIPs for WireGuard or the “Send all traffic” option for IKEv2, add VPN tunnel DNS servers.
- Login/password asked every time. Solution: enable credential saving if your security policy allows.
✅ Check: Visit an IP info website and ensure it shows your VPN-assigned IP. Disconnect VPN and confirm your IP changes back. This confirms basic routing is correct.
Step 3: Enable DNS Leak Protection and On-Demand
Goal
Ensure DNS requests always go through the VPN and that the VPN connects automatically on the right networks. This reduces leak risk before applying the system kill switch.
Detailed Instructions
- In WireGuard, open the profile and make sure there's a DNS section (like DNS = 10.14.0.1 or your tunnel's DNS address). If missing, add recommended servers from your config so the client sets DNS when connecting.
- In OpenVPN (Tunnelblick), ensure your .ovpn config has a directive to set DNS via push from the server (e.g., dhcp-option DNS 10.14.0.1). If the server doesn’t push DNS, configure static DNS settings in macOS after connecting: Network → VPN Adapter → DNS.
- In IKEv2, check the server provides DNS via config. If needed, set DNS manually in the VPN connection settings.
- Enable On-Demand (if available). WireGuard offers “Activate on demand” and SSID/network rules. Tunnelblick lets you use “Connect when computer starts” or something similar in Viscosity. In IKEv2, turn on “Connect automatically” if macOS Sequoia offers this.
- Test that after rebooting your Mac, the client auto-starts the tunnel the first time it accesses a network.
Tip: If you use a domain name as Endpoint, switch to the server's IP during kill switch setup. This ensures tunnel establishment isn’t blocked by inaccessible external DNS. Once debugging is done, you can revert to the domain and add a PF rule allowing DNS requests only for initial resolution. For simplicity, this guide assumes a fixed server IP.
Expected Result
DNS requests go through the tunnel and VPN connects automatically. This isn't the final kill switch but already cuts down many leak paths.
Possible Issues and Solutions
- DNS still leaks. Cause: system DNS priorities. Solution: disable third-party DNS resolvers on active interface or use matchDomains in supported clients.
- Auto-connect doesn’t trigger. Cause: policy conflicts. Solution: review your On-Demand settings and macOS auto-launch permissions.
✅ Check: Run scutil --dns and confirm the active resolver is your VPN tunnel address. Disconnect VPN and verify the resolver changes. Then reconnect VPN and confirm site access.
Step 4: System Kill Switch via PF: Basic Setup
Goal
Create a basic PF (Packet Filter) configuration that blocks all outgoing traffic except the allowed: to the VPN server during tunnel establishment and through the VPN interface after it's up.
How It Works
We'll enable the system PF and define rules: blocking everything by default, allowing only traffic via the utun interface, and permitting outgoing packets to the VPN server IP and port on your physical interface to establish the tunnel. This ensures that if the tunnel drops, your traffic won’t leak out, as no connections besides the VPN server and tunnel interface are permitted.
Prepare Your Data
- Note your VPN server’s IP address, e.g., 203.0.113.10.
- Note ports in use: WireGuard uses 51820/UDP; OpenVPN usually 1194/UDP (or your custom port); IKEv2 uses 500/UDP and 4500/UDP.
- Find your primary physical interface: run route -n get default and look for “interface: en0” or “en1”. Note this name (e.g., en0).
Create a PF Anchor File
- Open Terminal.
- Create the anchor file: sudo nano /etc/pf.anchors/vpn-killswitch.
- Paste the following rule lines, replacing values with your details. Treat each line as a separate item for clarity:
- set block-policy drop
- set skip on lo0
- block all
- pass quick on utun0 all keep state
- pass quick on utun1 all keep state (in case your client uses a different utun; add utun2, utun3 if needed)
- pass out quick on en0 proto udp to 203.0.113.10 port 51820 keep state (for WireGuard)
- pass in quick on en0 proto udp from 203.0.113.10 port 51820 keep state (optional for return packets, though stateful covers this)
- pass out quick on en0 proto udp to 203.0.113.10 port 500 keep state (for IKEv2)
- pass out quick on en0 proto udp to 203.0.113.10 port 4500 keep state (for IKEv2)
- pass out quick on en0 proto udp to 203.0.113.10 port 1194 keep state (for OpenVPN UDP)
- pass out quick on en0 proto tcp to 203.0.113.10 port 443 keep state (for OpenVPN TCP 443 if used)
Save and exit. Only include ports and protocols you actually use to narrow the attack surface.
Load the Anchor in the Main pf.conf
- Edit the main config: sudo nano /etc/pf.conf.
- Add these lines at the end: anchor "vpn-killswitch" and below load anchor "vpn-killswitch" from "/etc/pf.anchors/vpn-killswitch".
- Save and close.
Enable PF and Test
- Check syntax: sudo pfctl -nf /etc/pf.conf (should show no errors).
- Load the config: sudo pfctl -f /etc/pf.conf.
- Enable PF if disabled: sudo pfctl -e.
- List active rules: sudo pfctl -sr; ensure your rules appear.
Tip: If unsure which utun your client will use, include rules like “pass quick on utun0..utun3”. It’s safe and makes maintenance easier for clients that generate multiple utuns on reconnect.
⚠️ Warning: Your internet may stop working now if VPN is disconnected and you haven’t allowed outgoing traffic to the server IP and ports. This is expected. Just connect your VPN client — traffic will route through the tunnel.
Expected Result
With PF on, internet only works when VPN is active. If VPN disconnects, all network traffic except an attempt to reach your VPN server IP and configured ports is blocked.
Possible Issues and Solutions
- Total loss of connection even when VPN is connected. Cause: wrong utun or missing pass rules on utun interfaces. Solution: check ifconfig for the active utun after connection, add it to rules, reload PF.
- Unable to establish VPN. Cause: outgoing traffic to the server IP and port isn’t allowed. Solution: add precise pass out rules on your physical interface.
- Domain Endpoint doesn’t resolve. Cause: DNS blocked before VPN connects. Solution: temporarily set IP in Endpoint or add selective DNS rules for your resolver, but be aware of leak risks.
✅ Check: Disconnect VPN — internet should stop. Connect VPN — internet restores. Run curl https://ifconfig.me to verify IP belongs to VPN. Disconnect and try again; request should hang or fail.
Step 5: Automate Kill Switch: Script and LaunchAgent
Goal
Ensure PF rules load and stay consistent across reboots and reconnects, and allow quick mode switching when needed.
Detailed Instructions
- Create a helper script to reload PF and check VPN interface status: sudo nano /usr/local/bin/vpn-ks-reload.sh.
- Paste each line below separately, replacing values as needed:
- #!/bin/sh
- /sbin/pfctl -nf /etc/pf.conf || exit 1
- /sbin/pfctl -f /etc/pf.conf
- /sbin/pfctl -e
- exit 0
- Save and close the file.
- Make it executable: sudo chmod +x /usr/local/bin/vpn-ks-reload.sh.
- Create a LaunchAgent to reload PF at user login: nano ~/Library/LaunchAgents/com.local.vpnks.reload.plist.
- Paste the following XML without indentation to avoid formatting errors:
- <plist version="1.0"><dict><key>Label</key><string>com.local.vpnks.reload</string><key>ProgramArguments</key><array><string>/usr/local/bin/vpn-ks-reload.sh</string></array><key>RunAtLoad</key><true/></dict></plist>
- Save the file and load the agent: launchctl load ~/Library/LaunchAgents/com.local.vpnks.reload.plist.
- Test by logging out and in again or running /usr/local/bin/vpn-ks-reload.sh manually to check for errors.
Tip: To temporarily disable the kill switch, run sudo pfctl -d. Remember, this disables system protection. Re-enable with sudo pfctl -e and sudo pfctl -f /etc/pf.conf.
Expected Result
After restarting your Mac, the PF rules activate automatically. They persist through VPN reconnects. You can also reload PF manually with a single command.
Possible Issues and Solutions
- LaunchAgent doesn’t start. Cause: plist syntax error. Solution: check syntax, reload the agent, check launchctl list and Console.app logs.
- PF disables after system update. Solution: run sudo pfctl -e again, reload config, reapply anchor if needed.
✅ Check: Reboot your Mac. After login, run sudo pfctl -sr to verify rules are active. Connect and disconnect VPN; internet should behave consistently (only accessible through VPN).
Step 6: Fine-Tuning Rules & Scenarios for Different Protocols
Goal
Make sure the kill switch works reliably with WireGuard, OpenVPN, and IKEv2, considering each protocol’s specifics, ports, and additional permissions.
WireGuard
- Allow only UDP to the server IP and port 51820 on your physical interface (e.g., en0). This is the minimal needed for tunnel creation.
- Ensure AllowedIPs is set to 0.0.0.0/0, ::/0 for full routing through utun.
- Check your config includes DNS inside the tunnel to prevent leaks.
OpenVPN
- If you use UDP: allow proto udp to IP port 1194 (or your port) on the physical interface.
- If you use TCP 443: allow proto tcp to IP port 443.
- Check server push DNS directives; alternatively set DNS manually when VPN adapter is active.
IKEv2
- Allow proto udp to IP port 500 and proto udp to IP port 4500 for outgoing traffic.
- Enable “Send all traffic” in IKEv2 settings. Without it, some traffic might bypass VPN, defeating the kill switch.
- Ensure DNS goes through the tunnel.
Tip: If you often switch networks (home, office, mobile hotspot), check if the physical interface (en0, en1) changes. If yes, add pass rules for each interface or use a “group egress” rule to allow outgoing traffic to your server IP regardless of interface.
Expected Result
PF rules are minimal and precise: only allowing tunnel establishment to your server and all traffic inside the tunnel. No extra exceptions.
Possible Issues and Solutions
- Connection drops when switching Wi-Fi networks. Cause: physical interface changed or provider blocked port. Solution: add rules for new interface; use OpenVPN TCP 443 if necessary.
- Internal resources (e.g., printers, NAS) unreachable. Cause: routes and AllowedIPs. Solution: add internal subnets to AllowedIPs or configure routing on the VPN server.
✅ Check: Test by connecting VPN, running ping and traceroute to external addresses. Disconnect VPN and confirm no new connections succeed and active streams stop.
Step 7: Advanced Section: Network Extension API Basics for Kill Switch
Goal
Understand how Network Extension API lets you set parameters controlling traffic behavior and enforce strict routing through the tunnel. This is optional and requires an Apple developer account.
Important Notes
Working with Network Extension on macOS may require an Apple Developer subscription and enabling entitlements. Some provider types, especially content filtering (NEFilter), need separate Apple approval. For personal use and debugging on your Mac, you can build an app with a Packet Tunnel Provider if you have basic entitlements. This section is for learning and experimental purposes.
Key Classes and Flags
- NEPacketTunnelProvider — entry point for a custom VPN client. Here you launch the tunnel and configure network settings.
- NEPacketTunnelNetworkSettings — within this object, specify IPv4/IPv6 settings, DNS, and routes. Key flags: includeAllNetworks = true and excludeLocalNetworks = true to force all traffic through the tunnel.
- NEVPNManager — manages On-Demand policy and VPN lifecycle in the system (for IKEv2 and Packet Tunnel).
- NEDNSSettings — sets DNS through the tunnel and matchDomains = [""] to capture all DNS requests.
Minimal Workflow in Xcode
- Create a new macOS app project in Xcode. Add a Network Extension target of type Packet Tunnel Provider.
- Enable entitlements like “Personal VPN.” Make sure Network Extensions with the needed provider is added in Signing & Capabilities.
- In the Packet Tunnel Provider code, create NEPacketTunnelNetworkSettings when setting up. Specify ipv4Settings with tunnel address (e.g., 10.14.0.2/32) and default routes (0.0.0.0/0). Set includeAllNetworks = true and excludeLocalNetworks = true.
- Add NEDNSSettings with DNS inside the tunnel and matchDomains = [""]. This routes all DNS via the tunnel.
- Save On-Demand policy with NEVPNManager, setting rules like “Connect” on all networks except trusted SSIDs, if any. For a kill switch, it's best not to exclude anything.
- Build and launch the app in local mode, installing VPN config in system. Confirm profile installation prompts if they appear.
Tip: If your goal is personal use (not app store release), keep the project and bundle ID under your Apple ID. It simplifies installation and profile management on your Mac.
Expected Result
You get a minimally functional custom client that directs all traffic and DNS through the tunnel by default. The system kill switch remains handled by PF as set earlier. Together, this offers double-layer protection.
Possible Issues and Solutions
- Missing entitlement. Solution: check Apple Developer subscription and add required capabilities in Xcode.
- Profile won’t install. Solution: check console logs, signing, and ensure app has VPN management rights.
- Conflict with existing client. Solution: don’t run system and custom clients simultaneously using the same tunnel.
✅ Check: Your VPN profile appears in macOS network settings. On connect, all routes go through the tunnel, DNS too. When you stop your client, PF will maintain traffic blocking.
Step 8: Comprehensive Testing: Traffic, DNS, Disconnects
Goal
Verify that the kill switch is solid: no outgoing connections bypass VPN, no DNS leaks, and internet blocks immediately on tunnel failure.
Test Plan
- Routing test: with VPN active, run curl https://ifconfig.me and note the IP. Disconnect VPN and confirm the request fails (blocked by PF).
- DNS test: with VPN active, run scutil --dns and check the resolver points to your tunnel. Run nslookup example.com and see which server responds. Disconnect VPN and verify resolution is blocked.
- Disconnect test: with VPN active, kill the client process or switch Wi-Fi to another network. Confirm internet doesn’t return until VPN reconnects.
- Recovery time test: measure seconds taken to reconnect VPN on network change. Confirm PF blocks traffic during that time.
- App test: open browser, messenger, media player; confirm they only work while VPN is connected.
Tip: For deep diagnostics, use tcpdump on your physical interface: sudo tcpdump -i en0 not port 51820 (for WireGuard). With correct PF config, you’ll see no user traffic leaks.
Expected Result
In all scenarios, no traffic or DNS leaks occur when VPN is off. On tunnel recovery, all apps regain access immediately.
Possible Issues and Solutions
- Some system services still access the network. Cause: missed PF exception. Solution: check sudo pfctl -vvsr and ensure no unexpected pass rules exist, especially those added by third-party apps.
- Reconnect delays. Solution: enable On-Demand in the client for faster network change reaction, simplify PF rules to minimal.
✅ Check: Summarize: no traffic without VPN, full functionality with VPN, DNS always via tunnel, no leaks on disconnect.
Result Verification
Checklist
- VPN client installed and configured (WireGuard, OpenVPN, or IKEv2).
- Imported correct config, known server IP and port.
- DNS routed via tunnel and auto-connect (On-Demand) enabled.
- PF anchor created and linked in /etc/pf.conf.
- PF enabled, rules active after reboot.
- Internet works only when VPN is active.
- Disconnect and DNS leak tests passed.
How to Test
- Run three cycles: VPN on—check IP and DNS; VPN off—no connections; VPN reconnect—access returns.
- Try different Wi-Fi and Ethernet networks.
- Review PF logs: sudo pfctl -vvsr and sudo pfctl -vvss (states).
Success Criteria
- Zero network connections without VPN (except allowed IPs and ports).
- No DNS responses when VPN off; stable resolution through tunnel when on.
- Fast app recovery after VPN reconnects.
Common Issues and Fixes
- Issue: Internet not working even with VPN on. Cause: wrong utun in PF rules. Fix: identify active utun via ifconfig after connection and add pass quick on utunX to anchor.
- Issue: VPN won't connect. Cause: PF blocking server traffic. Fix: add precise pass out quick on enX proto udp/tcp to IP port NNNN rule per protocol.
- Issue: DNS leak. Cause: DNS not routed through tunnel. Fix: add DNS to WireGuard config; for OpenVPN, use pushed or assigned DNS in VPN adapter.
- Issue: PF disables after system update. Cause: system resets services. Fix: run sudo pfctl -e and reload config; ensure LaunchAgent is active.
- Issue: Some apps bypass VPN. Cause: routing exceptions. Fix: check AllowedIPs, avoid split-tunneling, use 0.0.0.0/0 and ::/0.
- Issue: Can't build Network Extension example. Cause: missing entitlements. Fix: subscribe to Apple Developer, enable capabilities in Xcode project.
- Issue: Connection drops between Wi-Fi networks. Cause: port/protocol blocked by new provider. Fix: use OpenVPN TCP 443 or have backup profile.
Additional Features
Advanced PF Settings
- Use table variables: create a table for server IP; allow with one rule so updating IP later is easy.
- Enable logging: record blocks for later analysis with Console.app or tcpdump -n -e -ttt -i pflog0.
- Granularity: if you use multiple protocols, separate them into different anchors and load dynamically.
Client Optimization
- In WireGuard, tune MTU (e.g., 1420–1440) to avoid fragmentation.
- In OpenVPN, enable tls-crypt and strong ciphers for better security.
- In IKEv2, use modern cipher suites and PFS if server supports.
What Else You Can Do
- MDM and profiles: if managing corporate devices, roll out managed profiles with On-Demand policies for fleet control.
- Backup scenarios: prepare a secondary VPN profile on a different port or protocol for networks with strict filtering.
- Monitoring: set up notifications on tunnel drop via system events or scripts checking interface state and writing to a local log.
Tip: If you travel often and encounter guest networks, keep two or three profiles with different ports and protocols handy. This helps maintain VPN access under various restrictions.
FAQ
- Can you create a kill switch without PF? Yes, some clients have built-in kill switches, but PF offers system-level protection independent of the client.
- Do I need a domain instead of an IP? Not necessarily. Using IP in PF is simpler and more reliable. Domains help with load balancing and address changes but require careful DNS exception handling.
- How do I find which utun to use? Connect VPN and run ifconfig. The active utun with non-zero stats is your tunnel interface.
- Can I use IPv6? Yes. Add ::/0 in routes and ensure your server supports IPv6. PF rules for IPv6 are similar but watch for protocols and addresses.
- What if I’m not in Apple Developer? No problem. The main kill switch with PF works without Network Extension. The advanced section is optional.
- Should PF always be enabled? Yes, if you want a persistent kill switch. Only disable temporarily when you understand the risks.
- How can I safely update configs? Disconnect VPN, update config, check PF syntax, connect VPN, reload PF. Always keep server IP and ports handy.
- Will local networks (printers, NAS) work? By default no, if all traffic routes through tunnel. Add exceptions in VPN routes or consider split-tunnel for specific subnets, knowing the risks.
- How do I check for WebRTC leaks? Disable WebRTC in your browser or use extensions. Most importantly, keep the system kill switch active with full routes.
- Can I automate profile switching? Yes. Use scripts or client features (WireGuard CLI), keeping PF untouched if server IPs are in allowed tables.
Conclusion
You’ve completed the full journey: chosen a protocol and client, imported config, set up DNS and On-Demand, enabled a system kill switch via PF, automated it, and tested various scenarios. The result is a reliable setup where your traffic goes out only via VPN and is fully blocked at the system level if the connection drops.
Next, you can tweak configs for multiple servers and protocols, deploy PF tables, dive deeper into Network Extension API for custom clients, or use managed profiles in corporate settings. With proper setup, you’ll achieve high resilience, predictability, security, and clear diagnostics through PF logs.
Tip: Periodically review your rules, especially after macOS updates, and verify behavior hasn’t changed. The PF reload script and the “Result Verification” checklist help ensure everything keeps working as intended.
⚠️ Warning: Avoid adding unnecessary PF exceptions “just in case.” The shorter and more precise your allowed list, the stronger and more reliable your kill switch, and the fewer surprises when switching networks.