How to Configure UFW Firewall Rules in Linux with Shell Script

Firewall configuration is one of the first lines of defense in securing Linux systems. Whether in an enterprise environment or managed service provider (MSP) context, automating firewall rule creation is key to enforcing consistent security policies at scale.

This post introduces a robust shell script designed to configure UFW (Uncomplicated Firewall) exceptions in Linux environments—an essential tool for IT administrators looking to standardize and secure their server estate efficiently.

Background

UFW is a popular front-end for iptables on Debian-based systems like Ubuntu. It’s widely used due to its user-friendly syntax and ability to manage firewall rules without diving into the intricacies of iptables. However, manual UFW rule configuration can become repetitive and error-prone, especially in environments where multiple rules must be deployed across various systems.

This script simplifies that process by enabling administrators to configure UFW firewall exceptions dynamically through a command-line interface. It includes input validation, dry-run testing, and contextual error messaging, making it ideal for automated deployments via platforms like NinjaOne.

The Script

#!/usr/bin/env bash

# Description: Adds a rule to the ufw firewall.
# 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).
#
# Release Notes: Initial Release
#
# Usage: [--interface <arg>] [--protocol <arg>] [--port <arg>] [--action <arg>] [--from <arg>] [--help|-h]
#
# Preset Parameter: --rule "ReplaceMeWithRuleName"
#   The name of the rule you would like to add to the firewall.

# Static variables
_space=" " # Space character

die() {
    local _ret="${2:-1}"
    test "${_PRINT_HELP:-no}" = yes && print_help >&2
    echo "$1" >&2
    exit "${_ret}"
}

echo_error() {
    echo "$@" 1>&2
}

begins_with_short_option() {
    local first_option all_short_options='iltafh'
    first_option="${1:0:1}"
    test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0
}

# Functions "validation::*" are for validating IP addresses
# Source: https://github.com/labbots/bash-utility/blob/master/src/validation.sh
# License: MIT - https://github.com/labbots/bash-utility/blob/master/LICENSE
validation::ipv4() {
    [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2
    declare ip="${1}"
    declare IFS=.
    # shellcheck disable=SC2206
    declare -a a=($ip)
    [[ "${ip}" =~ ^[0-9]+(\.[0-9]+){3}$ ]] || return 1
    # Test values of quads
    declare quad
    for quad in {0..3}; do
        [[ "${a[$quad]}" -gt 255 ]] && return 1
    done
    return 0
}

validation::ipv6() {
    [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2

    declare ip="${1}"
    declare re="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|\
([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|\
([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|\
([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|\
:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|\
::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|\
(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|\
(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"

    [[ "${ip}" =~ $re ]] && return 0 || return 1
}

are_addresses_valid() {
    # Convert the addresses to an array
    IFS=',' read -r -a addresses <<<"$1"

    for address in "${addresses[@]}"; do
        if validation::ipv4 "$address"; then
            # IPv4 address is valid
            continue
        elif validation::ipv6 "$address"; then
            # IPv6 address is valid
            continue
        else
            # Address is not valid
            echo_error "[Error] Invalid IP address: '$address'."
            return 1
        fi
    done
    # All addresses are valid
    return 0
}

is_port_in_range() {
    local port="${1}"
    # Check if the port range is valid 1-65535
    if ! [[ "${port}" -ge 1 ]] && [[ "${port}" -le 65535 ]]; then
        # Port range is not valid
        echo_error "[Error] Invalid port range: '${port}'."
        return 1
    fi
    return 0
}

are_ports_valid() {
    # Convert the ports to an array
    local port_regex='^[0-9]+$'
    local port_range_regex='^[0-9]+:[0-9]+$'
    IFS=',' read -r -a _ports <<<"$1"

    for port in "${_ports[@]}"; do
        if [[ "${port}" =~ $port_regex ]]; then
            # Port is a single port
            if ! is_port_in_range "${port}"; then
                # Port is not valid
                return 1
            fi
        elif [[ "${port}" =~ $port_range_regex ]]; then
            # Port range format
            # Check if the left and right sides of the range are valid single ports
            local IFS=':'
            read -r left right <<<"${port}"
            if ! is_port_in_range "${left}"; then
                # Port range is not valid
                return 1
            fi
            if ! is_port_in_range "${right}"; then
                # Port range is not valid
                return 1
            fi
        elif ! is_port_in_range "${port}"; then
            # Range of a single port
            # Port is not valid
            return 1
        fi
    done
    # All ports are valid
    return 0
}

is_root() {
    if [[ $EUID -ne 0 ]]; then
        die "[Error] This script must be run as root." 1
    fi
}

build_ufw_params() {
    local _param_action=$1 # Can be allow,deny,reject
    if ! are_ports_valid "$2"; then
        echo_error "[Error] Invalid port found in: '$2'."
        return 1
    fi
    local _param_port=$2 # Can be and empty string or an array
    declare OIFS=$IFS
    declare IFS=','
    read -r -a _param_port <<<"${_param_port}"
    local _param_protocol=$3  # Can only be tcp, udp, both, or any. Both requires double the rules
    local _param_interface=$4 # Can be and empty string or an array
    if ! are_addresses_valid "$5"; then
        echo_error "[Error] Invalid Address found in: '$5'."
        return 1
    fi
    local _param_from=$5 # Can be and empty string or an array
    read -r -a _local_param_from <<<"${_param_from}"
    IFS=$OIFS
    local _param_comment=$6 # Can only be a string

    if [[ -n "${_param_interface}" ]]; then
        _param_interface="in on ${_param_interface}"
    else
        _param_interface=""
    fi

    declare _rules=""
    if [[ -n "${_param_port[*]}" ]]; then
        if [[ "${_param_protocol}" == "Both" ]]; then
            # Add TCP and UDP rules
            # For each port in _param_port
            for _port in "${_param_port[@]}"; do
                if ((_port > 65535)) || ((_port < 1)); then
                    die "[Error] Invalid port range: '${_port}'. Ports must be between 1 and 65535." 1
                fi

                # Check if the from field is empty
                if [[ -z "${_param_from}" ]]; then
                    # Add TCP and UDP rules for any
                    _rules+="${_param_action} ${_param_interface} proto tcp from any port ${_port} ${_param_comment};"
                    _rules+="${_param_action} ${_param_interface} proto udp from any port ${_port} ${_param_comment};"
                else
                    # Add TCP and UDP rules for each IP address
                    # Check if the from field is empty
                    if [[ -z "${_local_param_from[*]}" ]]; then
                        # Add TCP and UDP rules for any
                        _rules+="${_param_action} ${_param_interface} proto tcp from any port ${_port} ${_param_comment};"
                        _rules+="${_param_action} ${_param_interface} proto udp from any port ${_port} ${_param_comment};"
                    else
                        # For each from in _local_param_from
                        for _ip_address in "${_local_param_from[@]}"; do
                            if [[ "${_ip_address}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
                                # IPv4 addresses
                                _rules+="${_param_action} ${_param_interface} proto tcp from ${_ip_address} port ${_port} ${_param_comment};"
                                _rules+="${_param_action} ${_param_interface} proto udp from ${_ip_address} port ${_port} ${_param_comment};"
                            elif [[ "${_ip_address}" =~ ^[0-9a-fA-F:]+$ ]]; then
                                # IPv6 addresses
                                _rules+="${_param_action} ${_param_interface} proto tcp from ${_ip_address} port ${_port} ${_param_comment};"
                                _rules+="${_param_action} ${_param_interface} proto udp from ${_ip_address} port ${_port} ${_param_comment};"
                            fi
                        done
                    fi
                fi
            done
        elif [[ "${_param_protocol}" == "TCP" ]]; then
            # Add TCP rule
            # For each port in _param_port
            for _port in "${_param_port[@]}"; do
                if ((_port > 65535)) || ((_port < 1)); then
                    die "[Error] Invalid port range: '${_port}'. Ports must be between 1 and 65535." 1
                fi

                # Check if the from field is empty
                if [[ -z "${_param_from}" ]]; then
                    # Add TCP and UDP rules for any
                    _rules+="${_param_action} ${_param_interface} proto tcp from any port ${_port} ${_param_comment};"
                else
                    # Check if the from field is empty
                    if [[ -z "${_local_param_from[*]}" ]]; then
                        # Add TCP and UDP rules for any
                        _rules+="${_param_action} ${_param_interface} proto tcp from any port ${_port} ${_param_comment};"
                    else
                        # For each from in _local_param_from
                        for _ip_address in "${_local_param_from[@]}"; do
                            if [[ "${_ip_address}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
                                # IPv4 addresses
                                _rules+="${_param_action} ${_param_interface} proto tcp from ${_ip_address} port ${_port} ${_param_comment};"
                            elif [[ "${_ip_address}" =~ ^[0-9a-fA-F:]+$ ]]; then
                                # IPv6 addresses
                                _rules+="${_param_action} ${_param_interface} proto tcp from ${_ip_address} port ${_port} ${_param_comment};"
                            fi
                        done
                    fi
                fi
            done
        elif [[ "${_param_protocol}" == "UDP" ]]; then
            # Add UDP rule
            # For each port in _param_port
            for _port in "${_local_param_from[@]}"; do
                if ((_port > 65535)) || ((_port < 1)); then
                    die "[Error] Invalid port range: '${_port}'. Ports must be between 1 and 65535." 1
                fi

                # Check if the from field is empty
                if [[ -z "${_local_param_from[*]}" ]]; then
                    # Add TCP and UDP rules for any
                    _rules+="${_param_action} ${_param_interface} proto udp from any port ${_port} ${_param_comment};"
                else
                    # For each from in _local_param_from
                    for _ip_address in "${_local_param_from[@]}"; do
                        if [[ "${_ip_address}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
                            # IPv4 addresses
                            _rules+="${_param_action} ${_param_interface} proto udp from ${_ip_address} port ${_port} ${_param_comment};"
                        elif [[ "${_ip_address}" =~ ^[0-9a-fA-F:]+$ ]]; then
                            # IPv6 addresses
                            _rules+="${_param_action} ${_param_interface} proto udp from ${_ip_address} port ${_port} ${_param_comment};"
                        fi
                    done
                fi
            done
        elif [[ "${_param_protocol}" == "Any" ]]; then
            # Add any rules
            # For each port in _param_port
            for _port in "${_param_port[@]}"; do
                if ((_port > 65535)) || ((_port < 1)); then
                    die "[Error] Invalid port range: '${_port}'. Ports must be between 1 and 65535." 1
                fi
                # For each from in _local_param_from
                local -a _ip_addresses=()
                if [[ -z "${_local_param_from[*]}" ]]; then
                    local _from=" from any"
                else
                    local _from=" from ${_param_from}"
                    for _ip_address in "${_local_param_from[@]}"; do
                        if [[ "${_ip_address}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
                            # IPv4 addresses
                            _rules+="${_param_action} ${_param_interface} proto any from ${_ip_address} port ${_port} ${_param_comment};"
                        elif [[ "${_ip_address}" =~ ^[0-9a-fA-F:]+$ ]]; then
                            # IPv6 addresses
                            _rules+="${_param_action} ${_param_interface} proto any from ${_ip_address} port ${_port} ${_param_comment};"
                        fi
                    done
                fi
            done
        fi
    fi

    echo "${_rules}"
}

ufw_apply_rules() {
    local _param_action=$1
    local _param_port=$2
    local _param_protocol=$3
    local _param_interface=$4
    local _param_from=$5
    local _param_comment=$6
    local _local_rules
    if ! _local_rules=$(build_ufw_params "${_param_action}" "${_param_port}" "${_param_protocol}" "${_param_interface}" "${_param_from}" "${_param_comment}"); then
        die "[Error] Failed to build UFW rules." 1
    fi
    declare OIFS=$IFS
    declare IFS=';'
    read -r -a _rules <<<"${_local_rules}"
    IFS=$OIFS
    _has_error=false
    # Dry run the rules
    for _rule in "${_rules[@]}"; do
        if [[ -n "${_rule}" ]]; then
            ufw_dryrun_command="ufw --dry-run ${_rule}"
            echo "[Info] Running: ${ufw_dryrun_command}"
            if ! eval "${ufw_dryrun_command}" >/dev/null; then
                echo_error "[Error] Dry run failed with: ${ufw_dryrun_command}"
                _has_error=true
            fi
        fi
    done
    if [[ "${_has_error}" == true ]]; then
        echo_error "[Error] One or more rules could not be applied."
        return 1
    fi
    # Apply the rules
    for _rule in "${_rules[@]}"; do
        if [[ -n "${_rule}" ]]; then
            ufw_command="ufw ${_rule}"
            echo "[Info] Running: ${ufw_command}"
            if eval "${ufw_command}"; then
                echo "[Info] Rule added successfully with: ${ufw_command}."
            else
                echo "[Info] ${ufw_command}"
                echo_error "[Error] Failed to add rule."
            fi
        fi
    done
}

# Set the default values
_arg_interface=
_arg_protocol=
_arg_port=
_arg_action=
_arg_from=

print_help() {
    printf '%s\n' "The general script's help msg"
    printf 'Usage: %s [-i|--interface <arg>] [-l|--protocol <arg>] [-t|--port <arg>] [-a|--action <arg>] [-f|--from <arg>] [-h|--help]\n' "$0"
    printf '\t%s\n' "-i, --interface: Interface (no default)"
    printf '\t%s\n' "-l, --protocol: TCP, UDP, or Both (no default)"
    printf '\t%s\n' "-t, --port: list of ports or ranges of ports, e.g. 800, 443, 500-505 (no default)"
    printf '\t%s\n' "-a, --action: Allow, Deny, Reject (no default)"
    printf '\t%s\n' "-f, --from: IP address (no default)"
    printf '\t%s\n' "-h, --help: Prints help"
}

parse_commandline() {
    while test $# -gt 0; do
        _key="$1"
        case "$_key" in
        -i | --interface)
            test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
            _arg_interface="$2"
            shift
            ;;
        --interface=*)
            _arg_interface="${_key##--interface=}"
            ;;
        -i*)
            _arg_interface="${_key##-i}"
            ;;
        -l | --protocol)
            test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
            _arg_protocol="$2"
            shift
            ;;
        --protocol=*)
            _arg_protocol="${_key##--protocol=}"
            ;;
        -l*)
            _arg_protocol="${_key##-l}"
            ;;
        -t | --port)
            test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
            _arg_port="$2"
            shift
            ;;
        --port=*)
            _arg_port="${_key##--port=}"
            ;;
        -t*)
            _arg_port="${_key##-t}"
            ;;
        -a | --action)
            test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
            _arg_action="$2"
            shift
            ;;
        --action=*)
            _arg_action="${_key##--action=}"
            ;;
        -a*)
            _arg_action="${_key##-a}"
            ;;
        -f | --from)
            test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
            _arg_from="$2"
            shift
            ;;
        --from=*)
            _arg_from="${_key##--from=}"
            ;;
        -f*)
            _arg_from="${_key##-f}"
            ;;
        -h | --help)
            print_help
            exit 0
            ;;
        -h*)
            print_help
            exit 0
            ;;
        *)
            _PRINT_HELP=yes die "[Error] Got an unexpected argument '$1'" 1
            ;;
        esac
        shift
    done
}

parse_commandline "$@"

# Check if ufw is installed
if ! command -v ufw &>/dev/null; then
    die "[Error] UFW is not installed!" 1
fi

# Check if we are running as root
is_root

# Check if ufw is enabled
if ! ufw status | grep -q "Status: active"; then
    die "[Error] UFW is not enabled!" 1
fi

# Get our script variables
if [[ -n "${interface}" ]] && [[ "${_arg_interface}" != "null" ]]; then
    _arg_interface="${interface}"
fi

if [[ -n "${from}" ]] && [[ "${_arg_from}" != "null" ]]; then
    _arg_from="${from}"
fi

if [[ -n "${protocol}" ]]; then
    _arg_protocol="${protocol}"
fi

if [[ -n "${port}" ]]; then
    _arg_port="${port}"
fi

if [[ -n "${action}" ]]; then
    _arg_action="${action}"
fi

# Get list of interfaces
interfaces=$(ip link show | awk -F '\:\ ' '{print $2}' 2>/dev/null | sed 's/ //g' | sort -u | tr '\n' ',' | sed 's/^,//' | sed 's/,$//')

# Check if the interface is valid
if [[ "${interfaces}" == *"${_arg_interface}",* ]]; then
    if [[ -z "${_arg_interface}" ]] || [[ "${_arg_interface}" == "null" ]]; then
        echo "[Info] Interface is empty, applying to all interfaces."
    else
        echo "[Info] Interface '${_arg_interface}' is a valid interface."
    fi
else
    die "[Error] Invalid interface '${_arg_interface}'." 1
fi

# Validate the type of protocol
if [[ "${_arg_protocol}" == "TCP" ]] || [[ "${_arg_protocol}" == "UDP" ]] || [[ "${_arg_protocol}" == "Any" ]] || [[ "${_arg_protocol}" == "Both" ]]; then
    echo ""
else
    die "[Error] Invalid protocol '${_arg_protocol}'." 1
fi

# Validate the action
if [[ "${_arg_action}" == "Allow" ]] || [[ "${_arg_action}" == "Deny" ]] || [[ "${_arg_action}" == "Reject" ]]; then
    echo "[Info] Action '${_arg_action}' is valid."
    if [[ "${_arg_action}" == "Allow" ]]; then
        _arg_action="allow"
    elif [[ "${_arg_action}" == "Deny" ]]; then
        _arg_action="deny"
    elif [[ "${_arg_action}" == "Reject" ]]; then
        _arg_action="reject"
    fi
else
    die "[Error] Invalid action '${_arg_action}'." 1
fi

# Create parts of the rule

# Port
if [[ -z "${_arg_port}" ]]; then
    die "[Error] Port is required." 1
fi

# Action
if [[ -z "${_arg_action}" ]]; then
    die "[Error] Action is required." 1
fi

# Comment
_comment="comment 'Created on $(date --utc) from NinjaRRM by script: Firewall - Configure UFW Exceptions - Linux'"

# Get the number of rules
declare -i _rule_count
_rule_count=$(ufw status numbered | tail -n 2 | head -n 1 | sed -e 's/\[//g' -e 's/\]//g' | awk '{ if ($1 ~ /^[0-9]+$/) print $1; else print "0"; }')

# Print the status of the firewall
echo "[Info] Current UFW status before adding rules:"
ufw status verbose

# Print the number of rules
echo "[Info] Number of rule before adding rules: ${_rule_count}"

# Apply the rules
if ufw_apply_rules "${_arg_action}" "${_arg_port}" "${_arg_protocol}" "${_arg_interface}" "${_arg_from}" "${_comment}"; then
    # Print the status of the firewall
    echo "[Info] Current UFW status after adding rules:"
    ufw status verbose
    # Print the number of rules
    _rule_count=$(ufw status numbered | tail -n 2 | head -n 1 | sed -e 's/\[//g' -e 's/\]//g' | awk '{ if ($1 ~ /^[0-9]+$/) print $1; else print "0"; }')
    echo "[Info] Number of rule after adding rules: ${_rule_count}"
else
    die "[Error] Failed to apply rules." 1
fi

 

Detailed Breakdown

Here’s a breakdown of the script’s core functionality:

Input Handling and Validation

The script accepts several arguments:

  • –interface: Specifies the network interface.
  • –protocol: TCP, UDP, Both, or Any.
  • –port: A list or range of ports.
  • –action: Allow, Deny, or Reject.
  • –from: Source IP addresses (supports both IPv4 and IPv6).

It validates all inputs to ensure proper format and range, especially for IP addresses and port numbers.

Build and Apply UFW Rules

The build_ufw_params function constructs UFW-compatible rules based on the inputs. It dynamically adapts to protocol type and source IP requirements, supporting multiple scenarios—whether rules apply to all interfaces or specific ones, and whether source restrictions are in place or not.

Once rules are constructed, ufw_apply_rules performs a dry run to catch any issues before actual application. If the dry run succeeds, it proceeds to execute the rules and prints the firewall status before and after the operation.

Root Privilege and UFW Checks

The script confirms it’s running with root privileges and that UFW is both installed and enabled—ensuring it’s only executed in valid environments.

This is a simplified version of what the script builds internally, providing flexibility and safety checks.

Potential Use Cases

Imagine an MSP onboarding a new Linux server for a financial services client. The server must allow TCP traffic on ports 443 and 22 only from specific IP ranges. Instead of logging into the server and manually adding rules, the technician runs the script with the required parameters via NinjaOne’s remote scripting tool. This guarantees consistent rule syntax and enables audit-friendly logging via embedded comments.

Comparisons

Compared to manually configuring UFW with ufw allow, this script offers several advantages:

  • Automation: Ideal for mass deployments.
  • Validation: Reduces risk of misconfiguration.
  • Readability: Generates human-readable commands.
  • Dry-Run Safety: Prevents risky rule changes.

While configuration management tools like Ansible or Puppet offer similar capabilities, this script offers a lightweight, standalone alternative suitable for single-node or ad-hoc use.

Start your free trial today and experience effortless ad-hoc remote support with NinjaOne—connect instantly and resolve issues in seconds.

Frequently Asked Questions

Can the script handle port ranges?

Yes, it supports both single ports and port ranges (e.g., 1000:2000).

Is IPv6 supported?

Absolutely. The script validates both IPv4 and IPv6 addresses.

What happens if UFW is not installed or enabled?

The script exits gracefully with an error message, avoiding partial execution.

Can I run this non-interactively?

Yes, it’s designed for both interactive and automated environments like NinjaOne or cron jobs.

What are the dependencies?

Besides standard Linux utilities, it requires UFW to be installed and enabled.

Implications

By automating UFW rule creation, IT administrators reduce the likelihood of human error and improve network security posture. Incorrect firewall configurations are a common vector for data breaches; scripts like this help enforce consistent rule sets across endpoints and servers. For MSPs, it’s a scalable solution that aligns with compliance standards and customer SLAs.

Recommendations

  • Test with Dry Run: Always validate the rule via dry run before live application.
  • Use Comments: Include detailed comments for auditing purposes.
  • Maintain Logs: Redirect script output to log files during automated runs.
  • Restrict IPs: Use the –from flag to limit exposure.
  • Integrate with RMMs: Tools like NinjaOne can schedule this script across devices.

Final Thoughts

Using a shell script to configure UFW firewall exceptions in Linux systems not only accelerates deployment but also enhances security and manageability. In MSP environments, leveraging tools like NinjaOne to automate this process allows for scalable, policy-driven network security enforcement. With validation, dry runs, and intelligent rule construction, this script is a valuable addition to any Linux administrator’s toolkit.

Whether you’re rolling out new servers or tightening security across existing infrastructure, this approach provides a safe, consistent, and professional method to enforce firewall policies.

Next Steps

Building an efficient and effective IT team requires a centralized solution that acts as your core service delivery tool. NinjaOne enables IT teams to monitor, manage, secure, and support all their devices, wherever they are, without the need for complex on-premises infrastructure.

Learn more about NinjaOne Remote Script Deployment, check out a live tour, or start your free trial of the NinjaOne platform.

Categories:

You might also like

×

See NinjaOne in action!

By submitting this form, I accept NinjaOne's privacy policy.

NinjaOne Terms & Conditions

By clicking the “I Accept” button below, you indicate your acceptance of the following legal terms as well as our 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 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).