
If you’ve ever tailed /var/log/secure on a fresh Fedora box and watched a wall of failed SSH login attempts scroll by within minutes of it going public, you already know why this guide exists. It doesn’t matter if your server has a single legitimate user or fifty — the moment a public IP is attached to port 22, automated bots start knocking. Some are lazy dictionary attacks. Others are more patient, spacing out attempts to dodge basic rate limits. Either way, leaving SSH exposed without any kind of adaptive defense is asking for trouble.
Fedora 44 ships with a modern systemd-journald logging stack, Firewalld as the default firewall manager, and nftables under the hood instead of legacy iptables. That combination changes a few things compared to older guides you might find floating around from Fedora 34 or CentOS 7 era tutorials — backend detection, ban actions, and log paths all behave slightly differently now. This article walks through installing Fail2Ban the right way on Fedora 44, integrating it properly with Firewalld, building jails that actually make sense for production workloads, and fixing the handful of issues that trip people up during setup.
This isn’t a copy-paste-and-hope guide. Every step here reflects how a production server should actually be configured — with attention to what breaks, what gets misconfigured, and what most tutorials skip entirely (looking at you, “just enable sshd jail and you’re done” articles). Whether you’re locking down a personal VPS, a WordPress hosting box, or a fleet of application servers, the principles and commands below apply directly.
By the end, you’ll have a working Fail2Ban installation tied into Firewalld’s rich rules, a sane baseline jail configuration, logging you can actually audit, and enough troubleshooting knowledge to fix it when (not if) something behaves unexpectedly.
What Fail2Ban Actually Does (and Why It Matters on Fedora 44)
Fail2Ban is an intrusion prevention framework, not a firewall itself. It watches log files — SSH auth logs, Nginx error logs, mail server logs, whatever you point it at — using regex-based filters, and when it detects a pattern matching malicious behavior (repeated failed logins, for instance), it triggers a “ban action.” That action typically inserts a temporary block rule into your firewall.
On Fedora 44, the natural pairing is Firewalld, since it’s the default firewall management layer and plays nicely with nftables. Fail2Ban doesn’t manipulate nftables directly in this setup — it calls Firewalld’s API to add rich rules, which then get translated into the underlying nftables ruleset. This is a cleaner separation of concerns than older iptables-based bans, and it avoids the rule-ordering headaches that used to plague people running Fail2Ban alongside a separately managed iptables chain.
One thing worth internalizing early: Fail2Ban is reactive, not preventive. It won’t stop a zero-day exploit or a sophisticated distributed attack spread across thousands of IPs. What it does do extremely well is neutralize the noise — the constant background hum of automated brute-force attempts that would otherwise fill your logs, consume CPU cycles on failed auth handling, and occasionally get lucky against weak credentials. For any internet-facing SSH service, that alone justifies the fifteen minutes it takes to set this up properly.
Prerequisites Before You Start
Before touching any configuration files, make sure your system is current. Fedora’s rapid release cadence means packages move fast, and running an outdated kernel or systemd version while troubleshooting firewall integration is a recipe for wasted time.
sudo dnf update && sudo dnf upgrade -y
Reboot if the kernel got updated — don’t skip this on a fresh install, since Fedora 44’s default kernel builds are frequently patched.
You’ll also want root or sudo access, a basic understanding of your logging setup (journald vs. traditional log files), and — critically — a secondary access method to your server before you start messing with SSH bans. This last point isn’t optional advice; it’s a lesson learned the hard way by plenty of admins who locked themselves out of their own box during initial testing. If you’re on a VPS, make sure your provider’s web-based console or out-of-band access works before you touch jail thresholds.
Step 1: Install Firewalld and Fail2Ban
Fedora 44 typically ships with Firewalld pre-installed and active, but don’t assume — verify it first.
sudo systemctl status firewalld
If it’s not installed or active, bring it in alongside Fail2Ban and the dedicated Firewalld integration package:
sudo dnf install firewalld fail2ban fail2ban-firewalld -y
That fail2ban-firewalld package matters more than it looks. It’s the piece that rewires Fail2Ban’s default ban action away from the legacy iptables-based method and points it at Firewalld’s rich-rule mechanism instead. Without it, Fail2Ban may fall back to iptables commands that either fail silently or conflict with Firewalld’s own nftables backend, leaving you with bans that appear active in fail2ban-client status but do nothing at the network layer.
Once installed, confirm both services are enabled and running:
sudo systemctl enable --now firewalld
sudo systemctl enable --now fail2ban
Check status on both:
sudo systemctl status firewalld fail2ban
You should see active (running) for both. If Fail2Ban immediately crashes or shows activating (auto-restart), don’t panic — that’s almost always a jail configuration syntax error, and we’ll cover diagnosing that shortly.
Step 2: Verify the Firewalld Integration
Fedora’s Fail2Ban package drops a pre-configured file that tells it to use Firewalld rich rules by default. Confirm this is actually in place before building your jails:
sudo grep -E '^(banaction|banaction_allports)' /etc/fail2ban/jail.d/00-firewalld.conf
You should see output resembling:
banaction = firewallcmd-rich-rules
banaction_allports = firewallcmd-rich-rules[actiontype=allports]
This confirms Fail2Ban is set to insert bans as Firewalld rich rules rather than raw iptables chains. If this file is missing, reinstall fail2ban-firewalld — it’s a strong sign the package didn’t land correctly during installation.
It’s worth pointing out why this matters practically: rich rules are visible and manageable through standard Firewalld commands (firewall-cmd --list-rich-rules), which means your bans show up in the same place as every other firewall rule on the box. That’s a meaningful improvement for auditability compared to older setups where Fail2Ban silently modified an iptables chain that nobody remembered to check during a security review.
Step 3: Never Edit jail.conf Directly
This is the single most important habit to build, and it’s one that separates people who understand Fail2Ban from people who fight it every time a package update overwrites their settings.
The file /etc/fail2ban/jail.conf is the package-maintained default configuration. It gets overwritten on every dnf update of the fail2ban package. If you edit it directly and an update comes through, your customizations vanish without warning — usually discovered at the worst possible time, like after a server reboot during an actual attack.
Instead, Fail2Ban is designed around a layered override system. Anything you want to customize goes into either:
/etc/fail2ban/jail.local— a single override file, or/etc/fail2ban/jail.d/*.conf— a directory of override snippets, read in alphabetical order.
Both approaches work, but for production servers managing multiple jails (SSH, Nginx, mail, custom application logs), the jail.d/ directory approach scales better — one file per jail keeps things organized and makes it trivial to disable a single service’s protection without touching anything else.
Step 4: Build Your Baseline Configuration
Create a local override file for your global defaults:
sudo nano /etc/fail2ban/jail.d/local.conf
Populate it with a baseline that reflects sane, production-ready defaults:
[DEFAULT]
backend = systemd
ignoreip = 127.0.0.1/8 ::1
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = ssh
backend = systemd
maxretry = 5
findtime = 600
bantime = 3600
A few decisions here deserve explanation, because copy-pasting without understanding them defeats the purpose.
backend = systemd tells Fail2Ban to read logs directly from the systemd journal rather than tailing a flat log file. Fedora 44, like most modern Fedora releases, logs SSH authentication events through journald by default rather than writing to /var/log/secure or /var/log/auth.log the way Debian-based systems do. If you skip this setting and Fail2Ban defaults to a file-based backend expecting a log file that doesn’t exist or isn’t populated, the sshd jail will report as enabled but will never actually catch anything — a silent failure that’s maddening to debug if you don’t know to check for it first.
ignoreip is your safety net. Always add your own trusted management IP ranges here before you do anything else. Getting banned from your own server because you fat-fingered a password three times during testing is a rite of passage nobody enjoys.
maxretry, findtime, and bantime work together as a sliding window. With the values above, an IP gets banned for one hour (3600 seconds) after 5 failed attempts within a 10-minute window (600 seconds). This is a reasonable starting point for general-purpose servers, but not a one-size-fits-all number — more on tuning this shortly.
Step 5: Validate Before You Restart
Before restarting the service, always validate the configuration syntax. This one step will save you from repeatedly restarting a broken service and wondering why nothing changed.
sudo fail2ban-client -t
If everything is valid, you’ll get a clean confirmation with no errors. If there’s a syntax problem — a missing bracket, a bad indentation, an unknown option — this command will point you directly to the offending line rather than making you dig through systemd logs after a failed restart.
Once validated, apply the configuration:
sudo systemctl restart fail2ban
Then confirm the sshd jail is actually active and watching:
sudo fail2ban-client status
sudo fail2ban-client status sshd
The second command shows currently banned IPs, total failed attempts, and total bans since the jail started — genuinely useful for a quick sanity check after any config change.
Step 6: Extend Protection Beyond SSH
SSH is the obvious first target, but production servers running web applications benefit enormously from extending Fail2Ban’s coverage. If you’re running Nginx with basic auth, WordPress with XML-RPC exposed, or any service that logs authentication failures in a predictable pattern, it’s worth adding a jail for it.
A common addition for WordPress or general web admins fighting credential-stuffing attempts against wp-login.php:
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 3600
This assumes the nginx-http-auth filter exists (it ships with Fail2Ban by default) and that your Nginx error log path matches. For custom WordPress brute-force protection, you’ll typically need a custom filter matching failed login patterns in your access logs — a topic broad enough to deserve its own dedicated write-up, but the jail structure above is the template you’d extend.
Real-World Scenario: The Traffic Spike Problem
Here’s a situation that catches people off guard on production servers: a legitimate traffic spike — say, a marketing campaign driving unusual login volume, or a misconfigured monitoring tool retrying failed health checks — can trigger Fail2Ban bans against IPs that aren’t actually malicious. This is especially common behind shared NAT gateways or corporate proxies, where dozens of legitimate users appear to originate from a single IP.
The fix isn’t to disable Fail2Ban during high-traffic periods (a surprisingly common panic response). It’s to tune findtime and maxretry thoughtfully for the specific service, and to whitelist known infrastructure IPs — load balancers, monitoring services, CI/CD runners — in ignoreip proactively rather than reactively after they get banned mid-incident.
Troubleshooting Common Issues
Fail2Ban service fails to start or keeps restarting.
Run sudo fail2ban-client -t first — nine times out of ten, it’s a config syntax error. Check journal logs for specifics:
sudo journalctl -u fail2ban -n 50 --no-pager
Jail shows as enabled but never bans anyone despite obvious failed logins.
This is almost always a backend/logpath mismatch. On Fedora 44, confirm backend = systemd is set for jails reading SSH logs, since there may be no populated /var/log/secure file to tail. Verify journald is actually recording auth failures with:
sudo journalctl -u sshd | grep -i fail
Bans aren’t actually blocking traffic.
Verify the ban action is correctly wired to Firewalld: check /etc/fail2ban/jail.d/00-firewalld.conf as shown earlier. Then confirm the rule actually landed:
sudo firewall-cmd --list-rich-rules
If nothing shows up despite Fail2Ban reporting an active ban, fail2ban-firewalld likely isn’t installed or Firewalld itself isn’t running.
SELinux is blocking Fail2Ban actions.
Fedora runs SELinux enforcing by default. If Fail2Ban logs permission-denied errors when trying to execute ban actions, check:
sudo ausearch -m avc -ts recent
Address the denials with audit2allow rather than disabling SELinux outright — disabling SELinux to solve a Fail2Ban problem is trading one security gap for a much bigger one.
Locked yourself out via SSH ban during testing.
This happens. Use your provider’s out-of-band console access, log in, and run:
sudo fail2ban-client set sshd unbanip YOUR_IP_ADDRESS
Then immediately add that IP to ignoreip in your jail configuration to prevent a repeat.