
Automate UFW Firewall Rules From a Config File
Before you run this
This guide gives you a small bash script that reads a plain-text config file and passes each line to ufw, so your firewall rules live in one readable, version-controllable file instead of your shell history. It adds the rules you list; it does not remove rules that aren't in the file.
- It needs root. UFW modifies the kernel firewall, so run the script with
sudo. It exits immediately if you aren't root. - This can lock you out over SSH. If you set a default
deny incomingpolicy and enable UFW without an allow rule for your SSH port, your live session is dropped and you may lose remote access to the box. Make sure your SSH rule is in the config and applied before you enable the firewall. When you runufw enableover SSH, UFW warns you and asks to proceed — read that prompt, don't reflexively hity. - Test first. Read the script before running it. Try it on a test VM or a spare cloud instance you can reach through a console (not only SSH) before you point it at anything you care about. Use the built-in dry-run described below to see what would happen before it happens.
- What changes, and how reversible it is. Adding rules is easily undone (
ufw delete). Enabling the firewall is undone withufw disable. The one thing to treat with care isufw reset, which wipes all rules back to install defaults — it does back up the existing rule files first, but don't run it casually.
Assumptions: Ubuntu 22.04 LTS (also fine on Debian and other Ubuntu releases), bash, and ufw already installed (it ships with Ubuntu). On RHEL/Alma the default firewall is firewalld, not UFW, so this guide doesn't apply there unless you've deliberately installed ufw.
The config file
Create a plain file — I'll use /etc/ufw-rules.conf. Each non-comment line is exactly the arguments you'd type after ufw. Blank lines and lines starting with # are ignored.
# /etc/ufw-rules.conf
# Each line below is passed verbatim to `ufw`.
# Replace the sample IPs/ports with your own.
# Rate-limit SSH (drops repeated connections from one source)
limit 22/tcp
# Web server
allow 80/tcp
allow 443/tcp
# Postgres only from a trusted management subnet
allow from 192.0.2.0/24 to any port 5432 proto tcp
192.0.2.0/24, the ports, and the protocols are placeholders — substitute your real values. Every line here uses standard UFW rule syntax (allow, deny, reject, limit, and the from … to any port … proto … form). If you need a rule you're not sure how to phrase, build it once by hand and confirm it with ufw status before you commit it to the file — see the UFW man page (man ufw) for the full grammar.
I keep default policy and enabling the firewall out of this file on purpose (more on that below), because those are one-time setup steps, not day-to-day rules.
The script
#!/usr/bin/env bash
# apply-ufw-rules.sh — apply UFW rules from a config file.
# Usage: sudo ./apply-ufw-rules.sh [/path/to/rules.conf]
# sudo DRY_RUN=1 ./apply-ufw-rules.sh # preview, change nothing
set -euo pipefail
CONFIG="${1:-/etc/ufw-rules.conf}"
DRY_RUN="${DRY_RUN:-0}"
if [[ $EUID -ne 0 ]]; then
echo "Run this as root (use sudo)." >&2
exit 1
fi
if [[ ! -r "$CONFIG" ]]; then
echo "Config not found or not readable: $CONFIG" >&2
exit 1
fi
while IFS= read -r line || [[ -n "$line" ]]; do
# Trim leading/trailing whitespace
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
# Skip blanks and comments
[[ -z "$line" || "$line" == \#* ]] && continue
if [[ "$DRY_RUN" == "1" ]]; then
echo "DRY RUN: ufw $line"
# --dry-run prints the resulting ruleset without touching anything
ufw --dry-run $line
else
echo "Applying: ufw $line"
# $line is intentionally unquoted so ufw sees separate arguments
ufw $line
fi
done < "$CONFIG"
Two things worth calling out. The $line is deliberately left unquoted so the shell splits it into the separate arguments ufw expects — that's why your config lines must be well-formed. And UFW is naturally idempotent: re-applying a rule that already exists prints "Skipping adding existing rule" and returns success, so running the script repeatedly is safe.
Save it, make it executable:
chmod +x apply-ufw-rules.sh
First run, in the right order
Preview before you change anything:
sudo DRY_RUN=1 ./apply-ufw-rules.sh /etc/ufw-rules.conf
--dry-run is a real UFW option; it prints the ruleset that would result and modifies nothing. Read that output. If it looks right, apply for real:
sudo ./apply-ufw-rules.sh /etc/ufw-rules.conf
Now the one-time setup. Set your default policies and enable the firewall only after you've confirmed your SSH allow/limit rule is in place:
sudo ufw default deny incoming # block inbound by default
sudo ufw default allow outgoing # allow the box to reach out
sudo ufw enable # answer the SSH warning prompt carefully
If you edit rules while UFW is already enabled, re-run the script and then reload:
sudo ./apply-ufw-rules.sh
sudo ufw reload
Verify it worked
Check the active ruleset:
sudo ufw status verbose
You should see Status: active, your default policies, and each rule from the config. For a version you can delete rules by, use the numbered view:
sudo ufw status numbered
That prints each rule with an index, e.g. [ 3] 443/tcp ALLOW IN Anywhere.
Undo and roll back
To remove a single rule, either restate it with delete:
sudo ufw delete allow 80/tcp
…or delete by number from the numbered list (numbers shift after each delete, so re-run status numbered between deletions):
sudo ufw delete 3
To turn the firewall off entirely without losing your rules:
sudo ufw disable
To wipe everything back to install defaults — this disables UFW and clears all rules (it backs up the existing rule files first), so treat it as the nuclear option:
sudo ufw reset
Keeping it maintained
Because the config file is now the source of truth, put it in version control alongside the script and review changes there. The script is additive by design — deleting a line from the config does not remove that rule from a running firewall. When you need a clean slate that exactly matches the file, ufw reset, re-apply the defaults, then re-run the script. That's the honest, predictable workflow; a script that silently reconciles and deletes "unknown" rules is easy to get wrong and easy to lock yourself out with, so I've left it out.
For the exact rule grammar and every option, man ufw on the box is authoritative — check it there rather than trusting any syntax from memory.
Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.
More about the author →Was this article helpful?
Tap a star — no sign-in needed.
Be the first to rate this article.
