You notice the noise first
You check /var/log/secure after a weekend away and see hundreds of Failed password lines from IPs you do not recognize. Your SSH daemon is open to the internet, and automated scanners are hammering it. You want to stop the noise and block the offending addresses automatically. You install fail2ban, but the default configuration does nothing, SELinux throws a denial, and the firewall rules never appear. The tool works, but it expects you to understand how it talks to Fedora's security stack.
What's actually happening
Fail2ban is not a firewall. It is a log watcher that translates repeated failures into firewall rules. The architecture splits into three distinct parts. The backend reads logs. On Fedora, the systemd backend reads directly from the journal instead of parsing flat text files. The filter defines what counts as a failure using regular expressions. The action decides what to do when the threshold is crossed. By default, the action calls firewalld to insert an iptables or nftables rule that drops packets from the offending IP.
Think of it like a hotel front desk. The log is the guest register. The filter is the manager's rule about how many times a guest can ask for a key before getting flagged. The action is the security guard who adds the guest to the building's access control list. If the guard does not have clearance to modify the access list, the rule fails. That clearance is where firewalld and SELinux come in.
The timing mechanics matter. findtime is the rolling window during which failures are counted. maxretry is the number of failures inside that window that triggers a ban. bantime is how long the IP stays blocked. If an IP fails three times in ten minutes, then waits two hours before trying again, it will not trigger a ban because the window reset. Fail2ban tracks each IP independently. The counter resets for that specific address once the findtime window passes without new failures.
The fix
Install the package and start the service. Fedora ships a reasonably safe default configuration, but it leaves all jails disabled until you explicitly opt in.
sudo dnf install -y fail2ban
# Install the package without prompting for confirmation
sudo systemctl enable --now fail2ban
# Start the daemon immediately and register it for boot
Never edit the shipped configuration files in /etc/fail2ban/jail.conf. Package updates will overwrite them. Create a local override file instead. The convention across Fedora is to keep user modifications in /etc/ and leave package defaults in /usr/lib/ or /etc/ with a .conf extension that gets ignored when a .local file exists.
sudo nano /etc/fail2ban/jail.local
# Create the override file that survives package upgrades
Add the following configuration. The [DEFAULT] section sets global behavior. The [sshd] section enables protection for OpenSSH.
[DEFAULT]
bantime = 600
# How long an IP stays blocked, in seconds
findtime = 3600
# The rolling window during which failures are counted
maxretry = 5
# Number of failures within findtime that triggers a ban
backend = systemd
# Read logs directly from the journal instead of flat files
banaction = firewallcmd-ipset
# Use firewalld's ipset backend for efficient rule management
[sshd]
enabled = true
# Activate this jail
port = ssh
# Match the service port defined in /etc/services
filter = sshd
# Use the built-in sshd regex filter
logpath = /var/log/secure
# Fallback path for the systemd backend if journal access fails
maxretry = 3
# Override global maxretry for SSH specifically
Save the file and reload the daemon. Do not restart the service unless you want to drop all active bans. A reload applies new configuration while keeping existing blocks intact.
sudo systemctl reload fail2ban
# Apply configuration changes without clearing active bans
Fail2ban relies on firewalld to actually block traffic. The firewallcmd-ipset action creates a dedicated IP set and tells firewalld to drop packets matching that set. This is faster than adding individual rules for every banned IP. When you have thousands of banned addresses, individual iptables rules slow down packet processing. An IP set uses a hash table, so lookup time stays constant regardless of how many addresses are blocked.
Verify that firewalld is running and that the default zone matches your network interface.
sudo firewall-cmd --state
# Confirm the firewall daemon is active
sudo firewall-cmd --get-default-zone
# Check which zone your interfaces are assigned to
If your interface is in the public zone, fail2ban will automatically apply rules there. Run firewall-cmd --reload after every manual rule change to keep the runtime and persistent configurations synchronized. Fail2ban handles its own reloads, but manual edits to firewalld can desync the state.
Verify it worked
Check the daemon status and list active jails. The output will show which jails are running and how many IPs are currently banned.
sudo fail2ban-client status
# Show all active jails and their ban counts
sudo fail2ban-client status sshd
# Drill into the SSH jail to see banned IPs and match counts
Test the ban mechanism safely. Trigger a few failed SSH logins from a secondary machine or a VM. Watch the journal for the ban event.
sudo journalctl -xeu fail2ban
# Monitor fail2ban logs with explanatory context
You will see lines indicating the IP was banned. Verify the firewall actually dropped the traffic.
sudo firewall-cmd --list-all
# Confirm the fail2ban-ssh ipset is referenced in the zone
If the IP set appears and the ban count increments, the pipeline is working. Reboot before you debug. Half the time the symptom is gone after a clean service restart and firewall sync.
Common pitfalls and what the error looks like
SELinux is the most common blocker on Fedora. Fail2ban needs permission to read log files and modify firewall rules. If the context on /var/log/secure or a custom log directory is wrong, the backend will fail to parse entries.
ERROR Failed during configuration: Have not found any log file for sshd jail
ERROR Unable to read logging configuration
Check the security context on the log file.
ls -Z /var/log/secure
# Display the SELinux context attached to the log file
The output should end with auditd_log_t or var_log_t. If it shows unconfined_u:object_r:user_home_t:s0 or something similar, restore the default context.
sudo restorecon -Rv /var/log/secure
# Recursively restore the default SELinux context
SELinux denials also appear in the journal. Read them before disabling the policy.
sudo journalctl -t setroubleshoot
# Filter for SELinux alert summaries
The one-line summary will tell you exactly which process was denied which permission. Run sudo ausearch -m avc -ts recent to see the full audit record if you need to generate a custom policy module. Trust the package manager. Manual file edits drift, snapshots stay.
Another frequent issue is the firewalld backend mismatch. If you installed fail2ban on a system using ufw or raw iptables, the default action will fail. Fedora uses firewalld by default. If you switched to nftables directly without firewalld, change the ban action to iptables-multiport or nftables-multiport in jail.local.
False positives happen when the regex matches legitimate traffic. The built-in sshd filter is conservative, but custom filters often cast too wide a net. Test your regex against a sample log file before deploying.
sudo fail2ban-regex /var/log/secure /etc/fail2ban/filter.d/sshd.conf
# Dry-run the regex against actual log lines
The tool prints a summary of matches and non-matches. Adjust the pattern until only actual failures trigger. If you are protecting a custom application, create a filter file in /etc/fail2ban/filter.d/ and reference it in jail.local. Always validate the pattern against real logs. A bad regex will ban your own monitoring scripts or internal CI runners.
When to use this vs alternatives
Use fail2ban when you need a flexible, log-driven intrusion prevention system that works across multiple services like SSH, Apache, and Dovecot. Use sshd built-in limits like MaxAuthTries and LoginGraceTime when you only care about SSH and want to reduce load at the daemon level. Use sshguard when you prefer a lightweight, journal-native alternative that shares ban lists across machines via multicast. Use a cloud WAF or provider-level firewall when your server sits behind a load balancer and you want to block traffic before it reaches your instance. Stay on fail2ban if you are already comfortable with regex filters and need granular control over ban durations and actions.