Combining Fail2ban with UFW or firewalld puts every ban into the same ruleset your firewall already manages, instead of a private chain you never look at. You keep one place to audit, one policy to reason about, and bans that survive a firewall reload. This guide points Fail2ban's ban actions at UFW and firewalld, adds a safe whitelist, and shows how to prove a ban actually landed.
Before you start
- A Linux VPS with root or sudo access. Examples assume you log in as
deployand prefix commands withsudo. - Fail2ban installed and running. If it is not, start with Protect SSH from brute-force attacks with Fail2ban.
- One firewall in charge: UFW on Ubuntu 22.04/24.04 and Debian 12, firewalld on AlmaLinux 8–9 and Rocky 8–9. Running both at once is a common source of confusing results.
- A second way in — the browser console in your VPS provider's control panel — in case a ban locks you out.
Check what you have before changing anything:
fail2ban-client -V
sudo ufw status verbose # Ubuntu and Debian
sudo firewall-cmd --state # AlmaLinux and RockyWarning: Changing banaction restarts Fail2ban and clears existing bans. Do it from a session you can afford to lose, and keep the console open in another tab.
How Fail2ban ban actions reach the firewall
Fail2ban never edits firewall rules directly. A jail calls an action, and that action runs a script from /etc/fail2ban/action.d/. The default banaction is usually iptables-multiport or nftables-multiport on Debian and Ubuntu, and firewallcmd-ipset or firewallcmd-rich-rules on the RHEL family.
That distinction matters. A ban inserted by iptables-multiport lives in Fail2ban's own chain, so ufw status will not show it and ufw reload can wipe it. Pointing the action at the firewall's own tooling fixes both problems.
See what you are using now:
sudo fail2ban-client -d | grep -i banaction
grep -r "banaction" /etc/fail2ban/jail.local /etc/fail2ban/jail.d/ 2>/dev/nullCombining Fail2ban with UFW and firewalld
Set the action once in the [DEFAULT] section of jail.local so every jail inherits it. Keep the file small and let the defaults do the rest.
UFW on Ubuntu and Debian
# /etc/fail2ban/jail.local
[DEFAULT]
banaction = ufw
banaction_allports = ufwThe ufw action inserts a deny rule for the offending address. Reload and inspect:
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
sudo ufw status numberedYou should see a DENY IN line for the banned address, placed above your allow rules. Fail2ban inserts at the top, so the ban beats a broad allow rule. That is useful, and it is also why a bad ban hurts.
Tip: UFW's own limit option and Fail2ban solve different problems. UFW rate-limits connections per address; Fail2ban reads logs and bans on behaviour. Running both is fine — see Advanced UFW rate limiting to prevent brute force attacks.
firewalld on AlmaLinux and Rocky
Use the firewalld rich-rule actions. The multiport variant blocks only the ports the jail watches; the allports variant blocks the address everywhere.
# /etc/fail2ban/jail.local
[DEFAULT]
banaction = firewallcmd-rich-rules[actiontype=<multiport>]
banaction_allports = firewallcmd-rich-rules[actiontype=<allports>]Apply and inspect:
sudo systemctl restart fail2ban
sudo firewall-cmd --list-rich-rules
sudo firewall-cmd --permanent --list-rich-rulesBans added this way are runtime rules. A firewall-cmd --reload clears them, and Fail2ban re-applies them from the log on the next match. If you need bans to persist across reloads, use the permanent variant of the action. The trade-offs are covered in Use Fail2ban with firewalld or nftables ban actions.
If you run plain nftables without firewalld, use nftables-multiport and keep your rules in a table Fail2ban can write to. See nftables: Complex Rule Chaining for Stateful Traffic Filtering.
Whitelist before you ban
A layered setup bans fast, so whitelist anything you cannot lose: your office address, your monitoring host, your backup server.
# /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.10
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 4wbantime.increment makes repeat offenders serve progressively longer bans, which is the whole point of layering. Tune the base values in Configure Fail2ban jails: bantime, findtime and maxretry in jail.local.
Warning: ignoreip entries bypass every jail. Never add a wide range such as 203.0.113.0/24 unless you control every address in it.
Check it works
Ban a test address from the command line, then look at the firewall itself.
sudo fail2ban-client set sshd banip 203.0.113.10
sudo ufw status | grep 203.0.113.10 # UFW
sudo firewall-cmd --list-rich-rules # firewalld
sudo fail2ban-client get sshd banipThen confirm the reverse path works and the rule disappears:
sudo fail2ban-client set sshd unbanip 203.0.113.10
sudo ufw status | grep 203.0.113.10 # should print nothingIf the address shows in fail2ban-client status sshd but not in the firewall, the action is not reaching the firewall tool. Read the Fail2ban log:
sudo tail -n 50 /var/log/fail2ban.logTroubleshooting
- Ban appears in Fail2ban but not in
ufw status—banactionis stilliptables-multiport. Setbanaction = ufwin[DEFAULT]ofjail.localand restart Fail2ban. - Bans vanish after
ufw reloadorfirewall-cmd --reload— the firewall rebuilt its ruleset. Re-apply withsudo fail2ban-client reload, or switch to a permanent firewalld action. - You locked yourself out — connect through the browser console in your VPS provider's control panel, then remove your own address from the ban list. The full procedure is in Check banned IPs and unban an address in Fail2ban.
- Real users keep getting banned — the filter is too broad for your traffic. See Fail2ban False Positive Resolution: Fine-Tuning Jails and Timeouts.
- Nothing is ever banned — the jail is disabled or the log path is wrong. Work through Fail2ban not banning? Common causes and fixes.
Next steps
- Feed web attacks into the same firewall: Protect Nginx with Fail2ban: bad bots, scanners and auth.
- Get told the moment a ban fires: Get email or Telegram alerts when Fail2ban bans an IP.
- Catch scanning before it becomes an attack: Network Hardening: Monitoring for Unauthorized Port Scans on the VPS.
