With increasing concerns around endpoint security, managing macOS firewall settings at scale is a critical responsibility for IT professionals and Managed Service Providers (MSPs). Whether you’re tasked with hardening corporate MacBooks or ensuring stealth network posture for field devices, having a consistent and automated way to enforce firewall configurations can significantly reduce vulnerabilities.
This post introduces a powerful shell script that enables IT admins to enable, disable, and configure the macOS firewall programmatically—ensuring compliance and eliminating manual errors across fleets.
Background
macOS includes a built-in firewall—controlled by socketfilterfw—that offers granular control over incoming network connections and stealth mode visibility. However, modifying these settings manually through the System Settings UI is not scalable for MSPs or enterprise IT departments managing hundreds or thousands of endpoints. Moreover, UI-based changes can be overridden or missed, leading to inconsistent security baselines.
This shell script is designed to programmatically configure the macOS firewall, supporting Ventura (macOS 13) and later. By using parameters like –firewallState, –blockAllIncomingConnections, and –stealthMode, IT professionals can enforce company policies, comply with security frameworks, and streamline endpoint hardening during onboarding, remote remediation, or audit preparation.
The Script
#!/usr/bin/env bash
#
# Description: This script configures the macOS firewall settings, including enabling/disabling the firewall, blocking all incoming connections, and setting stealth mode to hide the computer from the network.
# By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
# Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms.
# Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party.
# Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider.
# Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations.
# Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks.
# Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script.
# EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
#
# Example: --firewallState "Enabled" --stealthMode "Enabled" --blockAllIncomingConnections "Yes"
#
# Verifying the current firewall state.
# The macOS firewall state is already set to 'enabled'.
# Successfully set the firewall state to 'enabled'.
#
# Verifying the current status of the 'Block all incoming connections' setting.
# Setting the block all incoming connections state to 'blocking'.
# Successfully set the block all incoming setting to 'blocking'.
#
# Verifying the current stealth mode state.
# The stealth mode state is already set to 'enabled'.
# Successfully set the stealth mode state to 'enabled'.
#
# Preset Parameter: --firewallState "Enabled"
# Enable or disable the overall firewall global state.
#
# Preset Parameter: --stealthMode "Enabled"
# Stealth mode hides the computer from the network by not responding to ICMP (ping) packets. Leave blank to not modify the setting.
#
# Preset Parameter: --blockAllIncomingConnections "Yes"
# Should all incoming connections be blocked or allowed. Leave blank to not modify the setting.
#
# Preset Parameter: --killSystemSettingsAppIfNecessary
# Changes to the firewall may not be visible in System Settings until the app is restarted.
#
# Preset Parameter: --help
# Displays some help text.
#
# Minimum OS Architecture Supported: macOS 13 (Ventura)
# Release Notes: Initial Release
_arg_firewallState=
_arg_blockAllIncomingConnections=
_arg_stealthMode=
_arg_killSystemSettingsIfNecessary="off"
_exitCode=0
print_help() {
printf '\n%s\n\n' 'Usage: [--firewallState|-f <arg>] [--stealthMode|-s <arg>] [--blockAllIncomingConnections|-b <arg>] [--killSystemSettingsAppIfNecessary|-k] [--help|-h]'
printf '%s\n' 'Preset Parameter: --firewallState "Enabled"'
printf '\t%s\n' "Enable or disable the overall firewall global state."
printf '%s\n' 'Preset Parameter: --stealthMode "Enabled"'
printf '\t%s\n' "Stealth mode hides the computer from the network by not responding to ICMP (ping) packets. Leave blank to not modify the setting."
printf '%s\n' 'Preset Parameter: --blockAllIncomingConnections "Yes"'
printf '\t%s\n' "Should all incoming connections be blocked or allowed. Leave blank to not modify the setting."
printf '%s\n' 'Preset Parameter: --killSystemSettingsAppIfNecessary'
printf '\t%s\n' "Changes to the firewall may not be visible in System Settings until the app is restarted."
printf '%s\n' 'Preset Parameter: --help'
printf '\t%s\n' "Displays this help menu."
}
die() {
local _ret="${2:-1}"
echo "$1" >&2
test "${_PRINT_HELP:-no}" = yes && print_help >&2
exit "${_ret}"
}
parse_commandline() {
while test $# -gt 0; do
_key="$1"
case "$_key" in
--firewallState | --firewallstate | -f)
test $# -lt 2 && die "[Error] Missing value for the required argument '$_key'." 1
_arg_firewallState=$2
shift
;;
--firewallState=*)
_arg_firewallState="${_key##--firewallState=}"
;;
--blockAllIncomingConnections | --blockAll | -b)
test $# -lt 2 && die "[Error] Missing value for the optional argument '$_key'." 1
_arg_blockAllIncomingConnections=$2
shift
;;
--blockAllIncomingConnections=*)
_arg_blockAllIncomingConnections="${_key##--blockAllIncomingConnections=}"
;;
--stealthMode | --stealthmode | -s)
test $# -lt 2 && die "[Error] Missing value for the optional argument '$_key'." 1
_arg_stealthMode=$2
shift
;;
--stealthMode=*)
_arg_stealthMode="${_key##--stealthMode=}"
;;
--killSystemSettingsIfNecessary | -k)
_arg_killSystemSettingsIfNecessary="on"
;;
--help | -h)
_PRINT_HELP=yes die
;;
*)
_PRINT_HELP=yes die "[Error] Received an unexpected argument '$1'" 1
;;
esac
shift
done
}
echo " "
parse_commandline "$@"
# If script form variables are used, replace the command line parameters with their value.
if [[ -n $firewallState ]]; then
_arg_firewallState="$firewallState"
fi
if [[ -n $blockAllIncomingConnections ]]; then
_arg_blockAllIncomingConnections="$blockAllIncomingConnections"
fi
if [[ -n $stealthModeState ]]; then
_arg_stealthMode="$stealthModeState"
fi
if [[ -n $killSystemSettingsAppIfNecessary && "$killSystemSettingsAppIfNecessary" == "true" ]]; then
_arg_killSystemSettingsIfNecessary="on"
fi
# Ensure the script is run with root permissions.
if [[ $(id -u) -ne 0 ]]; then
_PRINT_HELP=no die "[Error] This script must be run with root permissions. Try running it with sudo or as the system/root user." 1
fi
# Trim whitespace from the firewall state argument.
if [[ -n "$_arg_firewallState" ]]; then
_arg_firewallState=$(echo "$_arg_firewallState" | xargs)
fi
# Ensure a valid firewall state is provided.
if [[ -z "$_arg_firewallState" ]]; then
_PRINT_HELP=yes die "[Error] Please provide a valid firewall state to set such as 'Enabled' or 'Disabled'." 1
fi
# Convert firewall state to lowercase.
if [[ -n "$_arg_firewallState" ]]; then
_arg_firewallState=$(echo "$_arg_firewallState" | tr '[:upper:]' '[:lower:]')
fi
# Validate the firewall state.
if [[ "$_arg_firewallState" != "enabled" && "$_arg_firewallState" != "disabled" ]]; then
_PRINT_HELP=yes die "[Error] The firewall state '$_arg_firewallState' is invalid. Please provide a valid firewall state such as 'Enabled' or 'Disabled'." 1
fi
# Process block all incoming connections argument if provided.
if [[ -n "$_arg_blockAllIncomingConnections" ]]; then
_arg_blockAllIncomingConnections=$(echo "$_arg_blockAllIncomingConnections" | xargs)
# Ensure a valid block all incoming connections state is provided.
if [[ -z "$_arg_blockAllIncomingConnections" ]]; then
_PRINT_HELP=yes die "[Error] Please specify whether to block all incoming connections (Yes) or allow all incoming connections (No). You can also leave this setting blank to not modify the current setting." 1
fi
# Convert block all incoming connections state to lowercase.
if [[ -n "$_arg_blockAllIncomingConnections" ]]; then
_arg_blockAllIncomingConnections=$(echo "$_arg_blockAllIncomingConnections" | tr '[:upper:]' '[:lower:]')
fi
# Validate the block all incoming connections state.
if [[ "$_arg_blockAllIncomingConnections" != "yes" && "$_arg_blockAllIncomingConnections" != "no" ]]; then
_PRINT_HELP=yes die "[Error] The block all incoming connections setting of '$_arg_blockAllIncomingConnections' is invalid. Please specify 'Yes' to block all incoming connections or 'No' to allow all incoming connections." 1
fi
# Ensure block all incoming connections is not set to yes when firewall is disabled.
if [[ "$_arg_firewallState" == "disabled" && "$_arg_blockAllIncomingConnections" == "yes" ]]; then
_PRINT_HELP=yes die "[Error] The block all incoming connections setting of '$_arg_blockAllIncomingConnections' is invalid. Unable to block all incoming connections while the firewall is disabled." 1
fi
fi
# Process stealth mode argument if provided.
if [[ -n "$_arg_stealthMode" ]]; then
_arg_stealthMode=$(echo "$_arg_stealthMode" | xargs)
# Ensure a valid stealth mode state is provided.
if [[ -z "$_arg_stealthMode" ]]; then
_PRINT_HELP=yes die "[Error] Please specify a valid stealth mode state such as 'Enabled' or 'Disabled'." 1
fi
# Convert stealth mode state to lowercase.
if [[ -n "$_arg_stealthMode" ]]; then
_arg_stealthMode=$(echo "$_arg_stealthMode" | tr '[:upper:]' '[:lower:]')
fi
# Validate the stealth mode state.
if [[ "$_arg_stealthMode" != "enabled" && "$_arg_stealthMode" != "disabled" ]]; then
_PRINT_HELP=yes die "[Error] The stealth mode state of '$_arg_stealthMode' is invalid. Please specify a valid stealth mode state such as 'Enabled' or 'Disabled'." 1
fi
# Ensure stealth mode is not enabled when firewall is disabled.
if [[ "$_arg_firewallState" == "disabled" && "$_arg_stealthMode" == "enabled" ]]; then
_PRINT_HELP=yes die "[Error] The stealth mode state of '$_arg_stealthMode' is invalid. Unable to disable the firewall and enable stealth mode." 1
fi
currentIncomingConnectionsSetting=$(/usr/libexec/ApplicationFirewall/socketfilterfw --getblockall | grep -i "disable")
if [[ -z "$currentIncomingConnectionsSetting" && "$_arg_stealthMode" == "disabled" && (-z "$_arg_blockAllIncomingConnections" || "$_arg_blockAllIncomingConnections" == "yes") ]]; then
_PRINT_HELP=yes die "[Error] The stealth mode state of '$_arg_stealthMode' is invalid. Unable to disable stealth mode when block all incoming connections is enabled." 1
fi
fi
# Verify the current firewall state.
echo "Verifying the current firewall state."
currentFirewallState=$(/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate | grep -e 'State = [0-9]*' -o | sed 's/[^0-9]//g')
# Determine the desired firewall state.
case "$_arg_firewallState" in
disabled)
firewallGlobalState="off"
if [[ "$currentFirewallState" == "0" ]]; then
updateFirewallGlobalState="false"
fi
;;
enabled)
firewallGlobalState="on"
if [[ "$currentFirewallState" == "1" || "$currentFirewallState" == "2" ]]; then
updateFirewallGlobalState="false"
fi
;;
esac
# Update the firewall state if necessary.
if [[ "$updateFirewallGlobalState" == "false" ]]; then
echo "The macOS firewall state is already set to '${_arg_firewallState}'."
else
echo "Setting the firewall state to '${_arg_firewallState}'."
if ! /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate "$firewallGlobalState"; then
_PRINT_HELP=no die "[Error] Failed to set the firewall state of '$_arg_firewallState' by setting the global state to '$firewallGlobalState'." 1
fi
fi
# Verify the firewall state was updated successfully.
currentFirewallState=$(/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate | grep -e 'State = [0-9]*' -o | sed 's/[^0-9]//g')
case "$_arg_firewallState" in
disabled)
if [[ "$currentFirewallState" != "0" ]]; then
_PRINT_HELP=no die "[Error] Failed to set the firewall state of '$_arg_firewallState' by setting the global state to '$firewallGlobalState'. The current state is '$currentFirewallState'." 1
fi
;;
enabled)
if [[ "$currentFirewallState" != "1" && "$currentFirewallState" != "2" ]]; then
_PRINT_HELP=no die "[Error] Failed to set the firewall state of '$_arg_firewallState' by setting the global state to '$firewallGlobalState'. The current state is '$currentFirewallState'." 1
fi
;;
esac
echo "Successfully set the firewall state to '${_arg_firewallState}'."
echo ""
# Process block all incoming connections if provided.
if [[ -n "$_arg_blockAllIncomingConnections" ]]; then
# Verify the current block all incoming connections setting.
echo "Verifying the current status of the 'Block all incoming connections' setting."
currentIncomingConnectionsSetting=$(/usr/libexec/ApplicationFirewall/socketfilterfw --getblockall | grep -i "disable")
case "$_arg_blockAllIncomingConnections" in
yes)
blockAllIncoming="on"
desiredIncomingStatus="blocking"
if [[ -z "$currentIncomingConnectionsSetting" ]]; then
updateBlockAllIncomingConnections="false"
else
updateBlockAllIncomingConnections="true"
fi
;;
no)
echo "[Warning] Setting the block all incoming setting to a state of 'disabled'."
blockAllIncoming="off"
desiredIncomingStatus="disabled"
if [[ -n "$currentIncomingConnectionsSetting" ]]; then
updateBlockAllIncomingConnections="false"
else
updateBlockAllIncomingConnections="true"
fi
;;
esac
if [[ "$updateBlockAllIncomingConnections" == "false" ]]; then
echo "The block all incoming state is already set to '${desiredIncomingStatus}'."
else
echo "Setting the block all incoming connections state to '${desiredIncomingStatus}'."
if ! /usr/libexec/ApplicationFirewall/socketfilterfw --setblockall "$blockAllIncoming"; then
_PRINT_HELP=no die "[Error] Failed to set the block all incoming state to '$desiredIncomingStatus'." 1
fi
fi
# Verify the block all incoming connections setting was updated successfully.
currentIncomingConnectionsSetting=$(/usr/libexec/ApplicationFirewall/socketfilterfw --getblockall | grep -i "disable")
case "$_arg_blockAllIncomingConnections" in
yes)
if [[ -n "$currentIncomingConnectionsSetting" ]]; then
_PRINT_HELP=no die "[Error] Failed to set the block all incoming state to '$desiredIncomingStatus'." 1
fi
;;
no)
if [[ -z "$currentIncomingConnectionsSetting" ]]; then
_PRINT_HELP=no die "[Error] Failed to set the block all incoming state to '$desiredIncomingStatus'." 1
fi
;;
esac
echo "Successfully set the block all incoming setting to '${desiredIncomingStatus}'."
echo ""
fi
# Process stealth mode if provided.
if [[ -n "$_arg_stealthMode" ]]; then
# Verify the current stealth mode state.
echo "Verifying the current stealth mode state."
currentStealthModeState=$(/usr/libexec/ApplicationFirewall/socketfilterfw --getstealthmode | grep -E "off|disable")
case "$_arg_stealthMode" in
disabled)
desiredStealthModeState="off"
if [[ -n "$currentStealthModeState" ]]; then
updateStealthModeState="false"
else
updateStealthModeState="true"
fi
;;
enabled)
desiredStealthModeState="on"
if [[ -z "$currentStealthModeState" ]]; then
updateStealthModeState="false"
else
updateStealthModeState="true"
fi
;;
esac
if [[ "$updateStealthModeState" == "false" ]]; then
echo "The stealth mode state is already set to '${_arg_stealthMode}'."
else
echo "Setting the stealth mode state to '${_arg_stealthMode}'."
if ! /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode "$desiredStealthModeState"; then
_PRINT_HELP=no die "[Error] Failed to set the stealth mode state of '$_arg_stealthMode' by setting the state to '$desiredStealthModeState'." 1
fi
fi
# Verify the stealth mode state was updated successfully.
currentStealthModeState=$(/usr/libexec/ApplicationFirewall/socketfilterfw --getstealthmode | grep -E "off|disable")
case "$_arg_stealthMode" in
disabled)
if [[ -z "$currentStealthModeState" ]]; then
_PRINT_HELP=no die "[Error] Failed to set the stealth mode state of '$_arg_stealthMode' by setting the state to '$desiredStealthModeState'." 1
fi
;;
enabled)
if [[ -n "$currentStealthModeState" ]]; then
_PRINT_HELP=no die "[Error] Failed to set the stealth mode state of '$_arg_stealthMode' by setting the state to '$desiredStealthModeState'." 1
fi
;;
esac
echo "Successfully set the stealth mode state to '${_arg_stealthMode}'."
echo ""
fi
# Check if the 'System Settings' application is running.
systemSettingsProcess=$(pgrep -x "System Settings")
# If the 'System Settings' application is running and the kill flag is not set, warn the user.
if [[ "$_arg_killSystemSettingsIfNecessary" != "on" && -n "$systemSettingsProcess" ]]; then
echo "[Warning] The 'System Settings' application is currently running with process ID (PID) '$systemSettingsProcess'."
echo "[Warning] You may need to restart this application for the firewall changes to become visible."
fi
# If the kill flag is set and the 'System Settings' application is running, kill the application.
if [[ "$_arg_killSystemSettingsIfNecessary" == "on" && -n "$systemSettingsProcess" ]]; then
for pid in $systemSettingsProcess; do
echo "[Warning] Killing the 'System Settings' application with process ID (PID) '$pid'."
# Attempt to kill the process and handle any errors.
if ! kill -9 "$pid"; then
echo "[Error] Failed to kill the application with process ID (PID) '$pid'." >&2
_exitCode=1
else
echo "Killed process with PID $pid."
fi
done
fi
# If the 'System Settings' application is not running, inform the user that the changes are complete.
if [[ -z "$systemSettingsProcess" ]]; then
echo "The 'System Settings' application is not currently running. The changes are now complete."
fi
exit "$_exitCode"
Detailed Breakdown
This script is structured to handle four major operations:
- Parse command-line arguments to determine what settings to apply.
- Validate user inputs for firewall state, stealth mode, and block-all settings.
- Apply changes using macOS’s socketfilterfw CLI tool.
- Optionally kill the ‘System Settings’ GUI to reflect real-time firewall changes.
Key Parameters
| Parameter | Purpose |
| –firewallState | Enables or disables the global firewall (Enabled / Disabled) |
| –blockAllIncomingConnections | Blocks all incoming traffic (Yes / No) |
| –stealthMode | Enables stealth mode (ignores ping and ICMP requests) |
| –killSystemSettingsAppIfNecessary | Force restarts System Settings so UI reflects new state |
| –help | Displays usage instructions |
This modular approach makes it ideal for integration with automation platforms like NinjaOne.
Potential Use Cases
Scenario: Remote Compliance Enforcement
A healthcare MSP manages 150+ macOS laptops used by clinicians working offsite. To comply with HIPAA, each device must block all incoming connections and operate in stealth mode. The MSP deploys this script via NinjaOne’s automation engine with the parameters:
| bash CopyEdit ./configure_firewall.sh –firewallState “Enabled” –stealthMode “Enabled” –blockAllIncomingConnections “Yes” –killSystemSettingsAppIfNecessary |
This ensures all devices are hardened to meet regulatory standards, without manual user intervention.
Comparisons
| Approach | Pros | Cons |
| This shell script | Full automation, repeatable, CLI-based validation | Requires root access |
| Manual UI Configuration | No scripting knowledge needed | Not scalable, prone to errors |
| MDM Profiles (Jamf, Kandji) | Centralized, compliant | Requires licensed MDM platform |
| Apple Configurator | Great for initial provisioning | Not feasible post-deployment |
While MDMs provide enterprise-level control, many organizations prefer lightweight, script-based tooling for flexibility and minimal overhead—especially when integrated into tools like NinjaOne.
Implications
Running this script has immediate implications on macOS network visibility and access control:
- Improved Endpoint Security: By enabling stealth mode and blocking inbound traffic, the attack surface is drastically reduced.
- Reduced Discovery Risk: Devices become invisible to ping sweeps and unsolicited probes.
- Compliance Readiness: Aligns with frameworks like CIS Benchmarks, NIST, and HIPAA.
However, aggressive settings may disrupt legitimate workflows, such as AirDrop, remote access tools, or developer environments. Careful planning is essential.
Recommendations
- Always test in a staging environment before mass deployment.
- Pair with logging/auditing scripts to confirm settings were applied.
- Use conditional logic in deployment tools to detect macOS version before execution.
- Educate end users if changes impact their network behavior (e.g., blocking incoming file shares).
Final Thoughts
Managing macOS firewall settings at scale is no longer optional—it’s a necessity in a world of remote work, compliance mandates, and evolving threats. This shell script provides a reliable, flexible way to configure the firewall in macOS, whether you’re looking to enable it, disable it, or fine-tune its behavior.
When combined with NinjaOne, IT teams can deploy this script seamlessly across their macOS fleet. NinjaOne’s policy automation, scripting support, and real-time monitoring make it the ideal platform for enforcing network security controls at scale—without the complexity of traditional MDM solutions.