Skip to content

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.

Published on 7 min read

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

AspectApplication Firewallpf (packet filter)
Filters onApplication identity (code signature)Addresses, ports, protocols, interfaces
DirectionIncoming onlyIncoming and outgoing
Default stateOffOff (Apple ships a mostly empty ruleset)
GUISystem Settings > Network > FirewallNone
MDM controlcom.apple.security.firewall payloadNo dedicated payload; deploy files plus a LaunchDaemon
Typical useBaseline on every MacSpecific 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.conf to 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

ScenarioRecommendation
Standard managed laptopApplication Firewall on, stealth mode on, via profile
Laptop that never serves anythingAdd block-all incoming
Need to restrict by port, subnet or interfaceAdd a pf anchor
Need to control outbound connections per appThird-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.

ServiceWhat it exposesRecommendation
Remote Login (SSH)TCP 22Off unless needed. If on, allow only specific users and use key authentication
Screen Sharing / Remote ManagementVNC / ARDOff unless your support process needs it
File SharingSMBOff on laptops
Remote Apple EventsApple events over the networkOff
Internet SharingRouting, turns on pf NATOff
Media Sharing, Content CachingVariousOff unless deliberately used
AirDropPeer-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 -E should 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.