macOS Firewall and pf: Application Firewall, pf and Sharing
Harden macOS networking with the Application Firewall, pf packet filter rules, disabled sharing services and managed encrypted DNS profiles.
A Mac that accepts connections from anyone on the local network is a Mac with more attack surface than it needs. macOS has two built-in filtering layers: the Application Firewall, which works per application, and pf, the packet filter inherited from BSD, which works per packet. On top of that, sharing services and DNS settings decide what the machine exposes and who it trusts. This guide explains what each layer does, how to configure and verify it, and when each one is the right tool. It applies to macOS 15 Sequoia and macOS 26 Tahoe.
The two firewalls in macOS
| Aspect | Application Firewall | pf (packet filter) |
|---|---|---|
| Filters on | Application identity (code signature) | Addresses, ports, protocols, interfaces |
| Direction | Incoming only | Incoming and outgoing |
| Default state | Off | Off (Apple ships a mostly empty ruleset) |
| GUI | System Settings > Network > Firewall | None |
| MDM control | com.apple.security.firewall payload | No dedicated payload; deploy files plus a LaunchDaemon |
| Typical use | Baseline on every Mac | Specific network rules, lab or high-risk systems |
The Application Firewall is a socket filter. When a process tries to listen for incoming connections, macOS checks whether that application may accept them. pf sits lower in the network stack and does not know which application owns a socket. The two layers complement each other.
Application Firewall
Enable it from the command line
The CLI tool is socketfilterfw, which is not on the default PATH:
FW=/usr/libexec/ApplicationFirewall/socketfilterfw
# Check the current state
sudo $FW --getglobalstate
# Turn the firewall on
sudo $FW --setglobalstate on
# Stealth mode: don't answer pings or probes to closed ports
sudo $FW --setstealthmode on
On macOS 15 and later, use socketfilterfw or a configuration profile. Do not read or write the old com.apple.alf preferences file directly. It is no longer a reliable source for firewall state.
Signed software and block-all mode
By default the firewall automatically lets signed software accept incoming connections. Two settings control this:
# Allow built-in (Apple-signed) software to receive connections
sudo $FW --setallowsigned on
# Allow downloaded software signed by a valid certificate authority
sudo $FW --setallowsignedapp on
For a stricter posture, turn off automatic allowance for downloaded signed apps (--setallowsignedapp off) and approve apps one at a time. Users with admin rights will then see a prompt the first time an app wants to listen.
Block all incoming connections is the strictest mode. It allows only the connections that basic services such as DHCP, Bonjour and IPsec need:
sudo $FW --setblockall on
This suits laptops that never provide any network service. It breaks inbound features such as Screen Sharing, AirPlay receiving and file sharing. That can be acceptable, or even the point.
Per-application rules
# List applications with explicit rules
sudo $FW --listapps
# Add an app, then block it from receiving connections
sudo $FW --add /Applications/Example.app
sudo $FW --blockapp /Applications/Example.app
Managing it with a configuration profile
In a fleet, set the firewall through MDM with the com.apple.security.firewall payload. A profile-managed firewall shows as managed in System Settings, and users cannot turn it off.
<dict>
<key>PayloadType</key>
<string>com.apple.security.firewall</string>
<key>PayloadIdentifier</key>
<string>com.example.baseline.firewall</string>
<key>PayloadUUID</key>
<string>REPLACE-WITH-UUID</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>EnableFirewall</key>
<true/>
<key>EnableStealthMode</key>
<true/>
<key>BlockAllIncoming</key>
<false/>
</dict>
For the surrounding profile structure and how to deliver it, see the MDM and configuration profiles guide.
pf: the packet filter
pf is the same packet filter family used on BSD systems. macOS uses it internally, for example for Internet Sharing. It ships with a ruleset that does almost nothing and is disabled until something turns it on.
How the default configuration is structured
/etc/pf.conf mostly consists of anchor references for Apple's own rules (com.apple/*), loaded from /etc/pf.anchors/. An anchor is a named sub-ruleset. Keeping your rules in your own anchor, rather than editing Apple's lines, keeps the change small and easy to review.
A minimal custom anchor, saved as /etc/pf.anchors/org.example.baseline:
# Default-deny inbound, allow outbound with state tracking
block in log all
pass in quick on lo0 all
pass in quick proto icmp6 all
pass in quick proto udp from any port 67 to any port 68
pass out quick all keep state
The ICMPv6 rule keeps IPv6 neighbor discovery working, and the UDP 67 to 68 rule lets DHCP replies in. Add pass in rules only for services the Mac really provides.
Reference the anchor from /etc/pf.conf by adding these two lines after Apple's existing anchor lines:
anchor "org.example.baseline"
load anchor "org.example.baseline" from "/etc/pf.anchors/org.example.baseline"
Load, enable and inspect
# Check syntax without loading
sudo pfctl -nf /etc/pf.conf
# Load the ruleset
sudo pfctl -f /etc/pf.conf
# Enable pf and take a reference token (prints "Token : <number>")
sudo pfctl -E
# Show loaded rules (main ruleset and your anchor)
sudo pfctl -sr
sudo pfctl -a org.example.baseline -sr
# Status and counters
sudo pfctl -si
# Release your reference when you're done
sudo pfctl -X <token>
macOS uses reference counting for pf. pfctl -E increments a counter and returns a token. pfctl -X <token> releases that reference, and pf stays enabled while any other component, such as Internet Sharing, still holds one. This avoids one tool turning pf off under another.
Making pf persistent
pf rules do not survive a reboot unless something reloads them at startup. The usual pattern is a LaunchDaemon that runs pfctl with your configuration at boot, delivered by MDM or your packaging tool. Two caveats:
- macOS updates can restore
/etc/pf.confto Apple's version. Check the anchor lines after every major upgrade, or have the LaunchDaemon load a separate configuration file that you own. - A wrong pf rule can lock you out of a remote Mac. Test on a machine you can reach physically.
When to use which
| Scenario | Recommendation |
|---|---|
| Standard managed laptop | Application Firewall on, stealth mode on, via profile |
| Laptop that never serves anything | Add block-all incoming |
| Need to restrict by port, subnet or interface | Add a pf anchor |
| Need to control outbound connections per app | Third-party outbound firewall |
| Server-like Mac (build agent, file server) | Application Firewall plus narrow pf rules for the served ports |
Outbound firewalls
Neither built-in layer filters outbound traffic per application. Tools such as LuLu (open source, from Objective-See) and Little Snitch (commercial, from Objective Development) fill that gap with a network extension. They are good for power users and high-risk users. In managed fleets they need approval of their system extension and network extension, usually by profile, and their prompts can overwhelm non-technical users.
Turn off sharing services you don't need
Every listener is attack surface. Review System Settings > General > Sharing, and on managed Macs enforce the settings by profile where possible.
| Service | What it exposes | Recommendation |
|---|---|---|
| Remote Login (SSH) | TCP 22 | Off unless needed. If on, allow only specific users and use key authentication |
| Screen Sharing / Remote Management | VNC / ARD | Off unless your support process needs it |
| File Sharing | SMB | Off on laptops |
| Remote Apple Events | Apple events over the network | Off |
| Internet Sharing | Routing, turns on pf NAT | Off |
| Media Sharing, Content Caching | Various | Off unless deliberately used |
| AirDrop | Peer-to-peer transfer | "Contacts Only" or off. Can be disabled with a Restrictions profile |
To check what is actually listening:
# All listening TCP sockets with owning process
sudo lsof -nP -iTCP -sTCP:LISTEN
# Remote Login state
sudo systemsetup -getremotelogin
On recent macOS, systemsetup may require the terminal app to have Full Disk Access. See the TCC guide.
Encrypted DNS
By default, DNS queries go in cleartext to whatever resolver DHCP hands out. On an untrusted Wi-Fi network, that leaks every hostname you look up and lets the network tamper with answers. Since macOS 11, a configuration profile can enforce DNS over HTTPS or DNS over TLS system-wide with the com.apple.dnsSettings.managed payload:
<dict>
<key>PayloadType</key>
<string>com.apple.dnsSettings.managed</string>
<key>PayloadIdentifier</key>
<string>com.example.baseline.dns</string>
<key>PayloadUUID</key>
<string>REPLACE-WITH-UUID</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>DNSSettings</key>
<dict>
<key>DNSProtocol</key>
<string>HTTPS</string>
<key>ServerURL</key>
<string>https://dns.example.net/dns-query</string>
<key>ServerAddresses</key>
<array>
<string>192.0.2.53</string>
</array>
</dict>
</dict>
Use TLS with ServerName instead of HTTPS with ServerURL for DNS over TLS. Choose a resolver whose logging and filtering policy fits your organization. Many organizations point this at a protective DNS service. Check the resolver order afterwards:
scutil --dns
Captive portals and split-DNS VPNs can interact badly with an enforced resolver. Test them before a broad rollout.
Verify it
FW=/usr/libexec/ApplicationFirewall/socketfilterfw
sudo $FW --getglobalstate # expect: enabled
sudo $FW --getstealthmode # expect: stealth mode on
sudo $FW --getblockall
sudo pfctl -si | head -n 5 # "Status: Enabled" if pf is active
sudo pfctl -a org.example.baseline -sr
sudo lsof -nP -iTCP -sTCP:LISTEN
sudo profiles show # confirm firewall/DNS payloads are installed
scutil --dns
From a second machine on the same network, a port scan of the hardened Mac should show filtered or no open ports, apart from services you have deliberately exposed.
Common pitfalls
- Assuming the firewall is on. It is off by default. Enforce it by profile and check it in compliance reporting. The benchmarks guide covers automated checks.
- Relying on the Application Firewall for egress. It does not filter outbound traffic.
- Editing Apple's lines in
/etc/pf.conf. Use your own anchor and expect upgrades to reset the file. - Forgetting
pfctl -X. Scripts that enable pf with-Eshould release their token when they no longer need it. - Leaving Remote Login on "All users". If SSH is required, restrict it to named accounts and follow the accounts and least privilege guide.
- Treating the firewall as the whole story. Network filtering adds to, and does not replace, Gatekeeper and notarization and timely software updates.