Guides
fail2ban with firewalld
On Fedora, RHEL, Rocky and AlmaLinux, fail2ban bans through firewalld out of the box. This page explains the firewalld actions, which zone the bans land in, how to see them, and the two traps: firewalld reloads and stopped firewalld.
To make fail2ban ban through firewalld, set banaction = firewallcmd-rich-rules and banaction_allports = firewallcmd-rich-rules in /etc/fail2ban/jail.local. On Fedora and on RHEL, Rocky or AlmaLinux with EPEL, the fail2ban-firewalld package already sets this in /etc/fail2ban/jail.d/00-firewalld.conf. Each ban becomes a rich rule in the public zone, visible with sudo firewall-cmd --list-rich-rules.
[DEFAULT]
banaction = firewallcmd-rich-rules
banaction_allports = firewallcmd-rich-rulesFedora and RHEL packages#
On Fedora and EPEL (Rocky, AlmaLinux, RHEL 8, 9 and 10), fail2ban is split into sub-packages. The fail2ban meta package pulls in fail2ban-server, fail2ban-sendmail and fail2ban-firewalld, which installs:
[DEFAULT]
banaction = firewallcmd-rich-rules
banaction_allports = firewallcmd-rich-rulesFedora switched this default from firewallcmd-ipset to rich rules in 2020, because the ipset action made firewalld fall back to legacy iptables. The optional fail2ban-systemd package adds jail.d/00-systemd.conf with backend = systemd. Install it on systems without /var/log/secure.
sudo dnf install epel-release # Rocky / Alma / RHEL only
sudo dnf install fail2ban fail2ban-systemd
sudo systemctl enable --now firewalld fail2banInstall details per distribution: RHEL, Rocky and AlmaLinux and Fedora.
fail2ban without firewalld#
If you run plain nftables instead, install only fail2ban-server (and fail2ban-systemd) and choose the action yourself:
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]Without fail2ban-firewalld, packaged 1.1.0 falls back to jail.conf's iptables default, so set banaction explicitly. See the nftables guide.
The firewalld actions compared#
| Action | How it bans | Visible with |
|---|---|---|
firewallcmd-rich-rules |
One runtime rich rule per IP and port in the zone | firewall-cmd --list-rich-rules |
firewallcmd-rich-logging |
Same, plus a rate-limited log prefix='f2b-<jail>' |
firewall-cmd --list-rich-rules, kernel log |
firewallcmd-ipset |
One direct rule matching an ipset f2b-<jail>; bans are set entries |
ipset list f2b-<jail>, firewall-cmd --direct --get-all-rules |
firewallcmd-multiport / firewallcmd-new |
Direct-interface chain f2b-<jail> jumped to from INPUT_direct for the jail's ports |
firewall-cmd --direct --get-all-rules |
firewallcmd-allports |
Same chain, jumped to for all traffic | firewall-cmd --direct --get-all-rules |
Rich rules are the default and the most "firewalld-native": they live in a zone and show up in normal firewalld tooling. Each ban adds one rule per port, which is fine for tens or hundreds of bans and gets slow with thousands.
ipset scales better: a single rule matches the whole set and each ban is a set entry. The ipset command must be installed. In fail2ban 1.1.1 the option that chooses who manages the set was renamed from ipsettype to ipsetbackend (ipset or firewalld). ipsettype now sets the set type (hash:ip, hash:net).
[DEFAULT]
banaction = firewallcmd-ipset
banaction_allports = firewallcmd-allportsThe direct-interface actions (multiport, allports, new, and ipset's rule) pass raw iptables rules through firewalld. firewalld treats the direct interface as a legacy feature. It still works with the nftables backend through iptables-nft, but rich rules or a plain nftables table are the cleaner choice on new systems.
Port ranges and recidive#
Jails that ban all ports pass port = 0:65535 to the action. firewalld writes ranges as 0-65535. Fedora's package patches the rich-rule action to translate : to -, while upstream fail2ban doesn't. On Debian or a source install with firewalld, use banaction_allports = firewallcmd-allports for recidive and other all-ports jails, or the rich rule will be rejected.
Zones: which zone gets the bans?#
firewallcmd-rich-rules adds its rules to zone = public by default. If your internet-facing interface is in another zone, the bans exist but never see the traffic. Check:
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --get-default-zoneIf the interface is in, say, external, pass the zone to the action:
[DEFAULT]
banaction = firewallcmd-rich-rules[zone=external]
banaction_allports = firewallcmd-rich-rules[zone=external]Zones bound to source addresses (firewall-cmd --zone=trusted --add-source=...) take precedence over interface zones. An attacker inside a source-bound zone isn't affected by a rule in public. That rarely matters for internet traffic, but it explains "banned but not blocked" on internal networks. The direct and ipset actions don't use zones: their rules sit in INPUT_direct and apply before zone processing.
View and verify bans#
# rich rules (default action)
sudo firewall-cmd --zone=public --list-rich-rules
# is one address banned?
sudo firewall-cmd --zone=public --list-rich-rules | grep 203.0.113.42
# ipset action
sudo ipset list f2b-sshd
# direct-interface actions
sudo firewall-cmd --direct --get-all-rulesA fail2ban rich rule reads like rule family="ipv4" source address="203.0.113.42" port port="ssh" protocol="tcp" reject type="icmp-port-unreachable", one per port in the jail. Compare with fail2ban's view:
$ sudo fail2ban-client banned 203.0.113.42
[['sshd']]To test the whole chain safely, ban a documentation address, look for it, and unban it:
sudo fail2ban-client set sshd banip 192.0.2.15
sudo firewall-cmd --list-rich-rules | grep 192.0.2.15
sudo fail2ban-client set sshd unbanip 192.0.2.15Trap 1: firewall-cmd --reload drops fail2ban's bans#
fail2ban adds its rich rules (and direct rules) to the runtime configuration only, never with --permanent. firewall-cmd --reload replaces the runtime configuration with the permanent one, so every ban disappears while fail2ban still lists the addresses as banned. After reloading firewalld, restart fail2ban so it re-applies current bans from its database:
sudo firewall-cmd --reload && sudo systemctl restart fail2banAnsible roles and admin scripts that reload firewalld should do the same.
Trap 2: "FirewallD is not running"#
If firewalld is stopped, every firewall-cmd call fails with FirewallD is not running, and fail2ban can't ban anything. /var/log/fail2ban.log shows the failing command with a non-zero return code. Fix it by starting firewalld:
sudo systemctl enable --now firewalld
sudo systemctl restart fail2banIf you deliberately don't use firewalld, remove the firewalld actions instead: uninstall fail2ban-firewalld (or override banaction in jail.local) and use nftables. Make sure no jail still names a firewallcmd-* action, since jail.local per-jail settings override [DEFAULT].
firewalld on Debian and Ubuntu#
Debian 13 and Ubuntu 24.04 default fail2ban to the nftables action even if firewalld is installed. That works: firewalld's nftables backend keeps its rules in its own table, and fail2ban uses inet f2b-table. A drop or reject in either one blocks the packet, so you don't have to switch. Bans just won't appear in firewall-cmd output. If you want them in firewalld, set the rich-rules action in jail.local (with firewallcmd-allports for all-ports jails, see above) and install ipset if you choose the ipset action.
FAQ#
Does fail2ban work with firewalld?#
Yes. fail2ban ships several firewalld actions, and on Fedora and RHEL-family systems the fail2ban-firewalld package makes firewallcmd-rich-rules the default. Each ban becomes a runtime rich rule in the public zone. firewalld must be running, or bans fail.
How do I set the fail2ban banaction for firewalld?#
Put banaction = firewallcmd-rich-rules and banaction_allports = firewallcmd-rich-rules under [DEFAULT] in /etc/fail2ban/jail.local, or install fail2ban-firewalld, which sets them in jail.d/00-firewalld.conf. Use firewallcmd-rich-rules[zone=<zone>] if your public interface isn't in the public zone.
Should I use firewallcmd-ipset or rich rules?#
Rich rules are the default and integrate with firewalld zones. They're fine for up to a few hundred bans. firewallcmd-ipset puts banned addresses in one ipset matched by a single rule, which scales to thousands, but it relies on firewalld's direct interface and the ipset tool.
Why did my fail2ban bans disappear after firewall-cmd --reload?#
fail2ban adds rules to firewalld's runtime configuration only. firewall-cmd --reload resets the runtime to the permanent configuration and drops them. fail2ban doesn't notice. Run systemctl restart fail2ban after every firewalld reload so it re-applies the active bans from its database.
What does "FirewallD is not running" mean in fail2ban?#
fail2ban is set to a firewallcmd-* action, but the firewalld service is stopped, so every ban command fails. Start it with systemctl enable --now firewalld, or switch banaction to nftables if you don't use firewalld.
Spotted something wrong or out of date? Tell us — every page is checked against the current fail2ban release.