A firewall management script for Ubuntu 24.04 (legacy iptables) that enforces IP whitelisting, Docker routing rules, Cloudflare IP allowlisting, and Fail2Ban integration — all driven by a single config file.
- Config-driven — all ports, flags, and paths live in
ufw-sync.conf; the script itself never needs editing - Public vs. whitelist ports — cleanly separate what is open to everyone vs. what requires a trusted IP
- Anti-lockout protection — auto-detects your current SSH session IP via three fallback methods and adds it to the whitelist before resetting UFW
- Docker-aware — writes correct
after.rules(NAT MASQUERADE + DOCKER-USER chain) so containers retain internet access through UFW - Cloudflare IP sync — fetches live IPv4 and IPv6 ranges from Cloudflare and allows them on public ports
- Fail2Ban integration — syncs
ignoreipfrom the whitelist file; restarts only when the config actually changes - Dry-run mode — preview every command without applying anything
- Automatic backups — saves
after.rulesbefore each run, keeps the last 10 copies
| Dependency | Notes |
|---|---|
| Ubuntu 24.04 | Tested on; should work on 22.04 |
ufw |
With legacy iptables (iptables-legacy) |
curl |
For Cloudflare IP fetch |
docker (optional) |
For Docker network subnet rules |
fail2ban (optional) |
For ignoreip sync |
Switch to legacy iptables if not already done:
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy/opt/ufw-sync/
├── ufw-sync.sh # Main script (do not edit for config changes)
└── backups/ # Auto-saved after.rules backups
/etc/ufw-sync/
└── ufw-sync.conf # ← Edit this to configure everything
/opt/configs/
└── allowed_ips.txt # Whitelist — one IP or CIDR per line
# 1. Create directories
sudo mkdir -p /opt/ufw-sync /etc/ufw-sync /opt/configs
# 2. Copy files
sudo cp ufw-sync.sh /opt/ufw-sync/ufw-sync.sh
sudo cp ufw-sync.conf /etc/ufw-sync/ufw-sync.conf
# 3. Set permissions
sudo chmod 700 /opt/ufw-sync/ufw-sync.sh
sudo chmod 600 /etc/ufw-sync/ufw-sync.conf
# 4. Add your trusted IPs to the whitelist
sudo nano /opt/configs/allowed_ips.txtAll settings live in /etc/ufw-sync/ufw-sync.conf.
# SSH port — leave empty to auto-detect from /etc/ssh/sshd_config
SSH_PORT=""
# Ports open to everyone (space-separated)
PUBLIC_PORTS_TCP="80 443"
PUBLIC_PORTS_UDP=""
# Ports open only to IPs in allowed_ips.txt
WHITELIST_PORTS_TCP="81 8080 8443"
WHITELIST_PORTS_UDP=""
# Paths
IP_FILE="/opt/configs/allowed_ips.txt"
LOG_FILE="/var/log/ufw_sync.log"
BACKUP_DIR="/opt/ufw-sync/backups"
# Features
ENABLE_CLOUDFLARE=1 # Sync Cloudflare IP ranges
ENABLE_DOCKER_NETWORKS=1 # Auto-add Docker subnet rules
DOCKER_MASQUERADE_RANGE="172.16.0.0/12"
ENABLE_FAIL2BAN=1 # Sync whitelist → fail2ban ignoreip
# 1 = preview only, no changes applied
DRY_RUN=0# Office
203.0.113.10
198.51.100.0/24
# Home
203.0.113.55
Lines starting with # and blank lines are ignored.
Preview (no changes applied):
sudo /opt/ufw-sync/ufw-sync.sh --dry-runApply:
sudo /opt/ufw-sync/ufw-sync.shCheck the log:
tail -f /var/log/ufw_sync.logOverride config file path:
sudo CONFIG_FILE=/tmp/test.conf /opt/ufw-sync/ufw-sync.sh --dry-runsudo crontab -e0 3 * * * /opt/ufw-sync/ufw-sync.sh >> /var/log/ufw_sync.log 2>&1
/etc/systemd/system/ufw-sync.service:
[Unit]
Description=UFW Sync
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/opt/ufw-sync/ufw-sync.sh
StandardOutput=append:/var/log/ufw_sync.log
StandardError=append:/var/log/ufw_sync.log/etc/systemd/system/ufw-sync.timer:
[Unit]
Description=UFW Sync — daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl enable --now ufw-sync.timerEach run follows this sequence:
- Load
/etc/ufw-sync/ufw-sync.conf - Detect current SSH session IP (3 fallback methods:
SSH_CLIENT→who→ss) - Auto-add detected IP to whitelist to prevent lockout
- Back up
/etc/ufw/after.rules - Rewrite
after.ruleswith correct NAT and DOCKER-USER rules - Reset UFW and set
deny incoming/allow outgoing - Open SSH port globally
- Open public ports (TCP/UDP) for everyone
- Open whitelist-only ports for each IP in
allowed_ips.txt - Allow Cloudflare IP ranges on public ports (if enabled)
- Allow Docker subnet IPs (if enabled)
- Enable UFW and reload
- Sync Fail2Ban
ignoreip(if enabled, only if changed)
The script rewrites /etc/ufw/after.rules on every run to ensure Docker containers can reach the internet through UFW without conflict:
- NAT MASQUERADE on
172.16.0.0/12for outbound container traffic - ESTABLISHED,RELATED conntrack rule so response packets are not dropped
- DOCKER-USER chain linked to
ufw-user-forwardfor UFW-managed forwarding rules
Important: If you manually edit
after.rules, your changes will be overwritten on the next run. Use the config file or extend the script instead.
Locked out after running the script
Boot into recovery or use a console session and run:
sudo ufw disable
sudo ufw resetThen add your IP to allowed_ips.txt and re-run with --dry-run first.
Cloudflare IPs not updating
Check network access from the server:
curl -fsS https://www.cloudflare.com/ips-v4If blocked, set ENABLE_CLOUDFLARE=0 in the config.
Docker containers have no internet access
Verify after.rules was written correctly:
sudo cat /etc/ufw/after.rules
sudo iptables -t nat -L POSTROUTING -n -vEnsure you are using iptables-legacy, not nftables.
Current IP not detected
If running via a non-SSH session (console, tmux detached), detection may fail. Add your IP manually to allowed_ips.txt before running.
allowed_ips.txtand the log file are created withchmod 600- The config file should also remain
600(set during installation) - The script validates IP format before applying rules — malformed entries are logged and skipped
- Fail2Ban config is only rewritten when
ignoreipactually changes, avoiding unnecessary restarts
MIT