Published 2026-10-22 · Updated 2026-10-22
Fail2ban Not Banning IPs: How to Find and Fix the Cause
Short answer: when fail2ban is running but bans nothing, the break is almost always at one of four points: the jail is not enabled or never loaded, it reads the wrong log or journal, its filter does not match the log lines, or a ban is created but the firewall action never produces a working rule. Check them in that order. Run sudo fail2ban-client status sshd first: if the failure counter stays at zero while logins are failing, the problem is the log source or the filter; if the ban counter grows but attackers still get through, the problem is enforcement. Finish with a test ban on a documentation address.
This guide is the diagnosis companion to our fail2ban setup guide, which covers installing and tuning jails. Here we assume the tool is installed and ask why it is silent.
In this article
Read the jail status to split the problem in two Is the service running and the jail enabled? Is the jail reading the right log or journal? Does the filter match your log lines? Thresholds, ignoreip and the clock Bans exist but nothing is blocked Run a test ban from start to finish It bans, but the attacks continue Frequently asked questionsQuick win (10 minutes)
- Run
sudo fail2ban-client statusand confirm your jail appears in the jail list. - Run
sudo fail2ban-client -tto check that the configuration loads without errors. - Run
sudo fail2ban-client get sshd logpath(orget sshd journalmatch) and confirm the source really contains SSH log lines. - Run
sudo fail2ban-regexagainst that log with--print-all-missedto see which failed logins the filter ignores. - Ban
203.0.113.9by hand, find it in the firewall rules, then unban it.
Read the jail status to split the problem in two
Start with the output of fail2ban-client status for your jail, because its counters tell you which half of the pipeline is broken. A jail has two halves: the detecting half (read a log, match failures, count them) and the enforcing half (run a ban action that changes the firewall). Most "not banning" reports are a break in one half only.
sudo fail2ban-client status
sudo fail2ban-client status sshd
The per-jail status shows the monitored file list (or journal matches), the failure counters (Currently failed, Total failed) and the ban counters (Currently banned, Total banned). Read them like this:
- The jail is not in the jail list: it is disabled or failed to load. Go to the next section.
- Total failed stays at 0 while your auth log shows failed logins: the jail is blind. Either it reads the wrong source or the filter does not match. See the log source and filter sections.
- Total failed rises but Total banned stays at 0: failures are seen but never cross the threshold, or the offender is exempt. See thresholds and ignoreip.
- Total banned rises but the attacker still connects: the ban is recorded but not enforced. See the enforcement section.
The fail2ban-client(1) manual page documents status with an optional jail name and flavor. Keep this fork in mind, because it saves you from checking the firewall when the real fault is a filter, and the other way around.
Is the service running and the jail enabled?
Confirm that the daemon answers and that your jail is among the loaded ones before looking at anything subtle. A daemon that is down bans nothing, and a jail that is not enabled is simply never started.
sudo systemctl status fail2ban
sudo fail2ban-client ping
sudo fail2ban-client status
The manual page describes ping as a test of whether the server is alive. If the service is dead or flapping, read the reason with sudo journalctl -u fail2ban -n 50 (see our journalctl entry) and then run the configuration test:
sudo fail2ban-client -t
The -t option tests the configuration, and a syntax error or a bad option in any file stops the new settings from applying. The shipped jail.conf sets enabled = false in its defaults and says by default all jails are disabled, so every jail you rely on needs enabled = true in a file you control. Per jail.conf(5), .local files are parsed after .conf files and the later value wins, so put your overrides in jail.local or jail.d/*.local:
# /etc/fail2ban/jail.local
[sshd]
enabled = true
Apply it without a full restart using sudo fail2ban-client reload, then run status again. To see what a jail really uses after all files are merged, query it directly instead of guessing:
sudo fail2ban-client get sshd maxretry
sudo fail2ban-client get sshd findtime
sudo fail2ban-client get sshd bantime
sudo fail2ban-client get sshd actions
Each of these get commands is documented in the client manual. For the anatomy of a jail, see the jail glossary entry.
Is the jail reading the right log or journal?
A jail can only count failures it can see, so check exactly what it is reading. Ask fail2ban rather than trusting the file you edited:
sudo fail2ban-client get sshd logpath
sudo fail2ban-client get sshd journalmatch
logpath lists the files the jail monitors and journalmatch shows the journal filter. Then open the source yourself and confirm that it contains SSH authentication lines from the last hour. A jail pointed at a file that is empty, missing or no longer written to will show zero failures forever.
File backend or systemd backend
According to jail.conf(5), backend defaults to auto, which tries pyinotify before polling, and may switch to the journal only if journalmatch is set for the jail or its filter. The systemd backend reads the journal and, as the page puts it, specifying logpath is not valid for that backend; it uses journalmatch instead. In the upstream Debian path file, sshd_log points at /var/log/auth.log and sshd_backend is systemd. So on a host where sshd writes only to the journal, a file-based jail reads nothing. The fix is to name the backend explicitly:
[sshd]
enabled = true
backend = systemd
Check the journal contains what the jail expects:
sudo journalctl -u ssh -u sshd --since "1 hour ago" -g "Failed|Invalid"
The journalctl(1) manual documents -u for a unit, --since for a time and -g for a regular expression on the message.
The ssh versus sshd unit name
The SSH service is not named the same everywhere. The Ubuntu server documentation uses ssh.service, while the sshd filter shipped with fail2ban lists _SYSTEMD_UNIT=sshd.service, _COMM=sshd and _COMM=sshd-session in its journalmatch. The shipped filter therefore matches on the process name as well, but a custom journalmatch copied from a tutorial for another distribution may match nothing on yours. Compare with systemctl list-units --type=service | grep -i ssh and fix the unit name in your override.
Paths that change or never existed
Per jail.conf(5), a logpath glob only covers files that exist when fail2ban starts. If a log is created later, moved to a new path after an upgrade, or the application now logs somewhere else, restart or reload fail2ban after fixing the path and confirm with get logpath again. If you rotate fail2ban's own log file, the client documents flushlogs as the command that flushes a file log target and reopens it for log rotation.
Does the filter match your log lines?
If the source is right but Total failed is still zero, the filter does not recognise your lines. Test the filter against real data with fail2ban-regex, which bans nobody. Per the fail2ban-regex(1) manual page, it takes a log (a string, a file name or systemd-journal) and a filter (a string, a filter or jail name, or a filter file path).
# log file
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf --print-all-missed
# systemd journal (needs systemd-python)
sudo fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf --print-all-missed
--print-all-missed prints every line the filter did not match, and --print-all-matched prints the matched ones. Read the missed lines for anything that is clearly a failed login. If you find such lines, the filter and the log format disagree, and that mismatch is your cause. Common reasons:
- The log lines come from a different daemon or message format than the filter expects, for example after an OpenSSH upgrade or with a custom
LogLevel. The defaultLogLevelin sshd_config(5) is INFO. - The filter has modes. The shipped
sshdfilter documentsnormal(default),ddos,extraandaggressive, selected withmodein the jail or in the filter name such assshd[mode=aggressive]. Read the comments in your installed filter file to see what each mode counts. - The date in the line cannot be parsed or is in an unexpected format.
--verbose-dateshows how dates were detected, and-dsets a custom date pattern.
When the source is the journal, -m (--journalmatch) lets you pass journalctl-style matches that override the filter file, which is a quick way to test a corrected unit name before changing the jail. Run the same command again after every fix. Only when the filter reports matches for real failed logins is it worth moving on.
Not sure whether your bans really reach the firewall?
A free, read-only server check reviews your exposed services and firewall state, so a decorative jail does not go unnoticed.
Get your free server check Talk to a security engineerThresholds, ignoreip and the clock
When failures are counted but no ban follows, look at the numbers and the exceptions. In the shipped jail.conf on the upstream project, bantime is 10m, findtime is 10m and maxretry is 5, but your distribution or your own files may differ, so read the live values with get sshd maxretry and get sshd findtime. According to jail.conf(5), maxretry is the number of failures that must occur within findtime before the current time to ban the address.
The consequence is that a slow scanner that tries a password every few minutes can stay under any threshold, and a very high maxretry never triggers on short bursts. Our setup guide explains how to tune these values; here the job is only to confirm they are what you think they are. For per-connection limits on the SSH side, see MaxAuthTries.
ignoreip and ignoreself
An address in ignoreip is never banned. The manual says it can hold IP addresses, CIDR masks, DNS names, or a file prefixed with file:. A forgotten broad range, such as a whole office network or a VPN subnet, hides exactly the traffic you are testing from. Also note that ignoreself is documented as a boolean that defaults to true, so the server's own addresses are never banned. If you test by failing logins from the machine itself, nothing will ever happen. Test from a different host instead, and list what is exempt with:
sudo fail2ban-client get sshd ignoreip
Time zone and clock problems
Fail2ban compares the timestamps in the log with the current time. The findtime window is measured before the current time, so lines stamped hours in the past or future can fall outside it. Two things can cause that: a wrong system clock, and log lines without a time zone that fail2ban interprets in the wrong zone. The logtimezone option is documented as forcing a time zone for log lines that do not have one. Compare a fresh log timestamp with the real time, and use -o short-iso on journalctl to see unambiguous stamps:
date
sudo journalctl -u ssh -u sshd -n 5 -o short-iso
If the two disagree by whole hours or minutes, fix time synchronisation or set logtimezone, then retest. For the wider habit of reading these logs routinely, see server monitoring and log analysis.
Bans exist but nothing is blocked
If Total banned rises, the detecting half works and the problem is the ban action. A decorative ban is worse than none, because it looks like protection. First see which action the jail runs and what it did:
sudo fail2ban-client get sshd actions
sudo fail2ban-client get sshd banip --with-time
sudo grep -iE "error|warning|fail" /var/log/fail2ban.log | tail -n 20
Errors from the action command usually show up in the fail2ban log. If your host sends fail2ban's own log elsewhere (check logtarget in fail2ban.conf and any fail2ban.local), read it there instead.
Match the action to your firewall
The default banaction is documented in jail.conf(5) as iptables-multiport, and banaction_allports as iptables-allports. Distribution path files can override this: the upstream paths-debian.conf sets banaction = nftables and banaction_allports = nftables[type=allports]. So check what your jail actually uses, and make sure the matching nftables, iptables or ufw rule shows up where you look. Our Linux firewall guide explains how the three layers relate, including why iptables output can come from nftables underneath.
# replace with the address you banned
sudo nft list ruleset | grep 203.0.113.9
sudo iptables -S | grep 203.0.113.9
sudo ip6tables -S | grep 203.0.113.9
sudo ufw status numbered
Run whichever commands apply and look for the address. Per the upstream nftables action file, its defaults are a table named f2b-table in the inet family with a chain named f2b-chain, so on a default nftables setup you should find them in the ruleset.
ufw specifics
Using banaction = ufw adds rules through ufw, and the ufw action file says you must run ufw enable first or the action has no effect. The ufw(8) manual states that rule ordering matters and the first match wins, and the action's add parameter defaults to prepend so a ban rule is placed ahead of a general allow rule. If you changed that, an earlier allow for port 22 can beat the ban. The same action file notes that ufw quickly processes packets for connections that already exist, so an already open session from a banned address can survive; its kill-mode option (ss or conntrack) closes those connections.
IPv6 and containers
- IPv6: ufw(8) says IPv6 must be enabled in
/etc/default/ufwfor IPv6 firewalling to work, andfail2ban.confhas anallowipv6option whose default isauto. If attackers arrive over IPv6, confirm your action handles IPv6 and useip6tables -Sor theinetnftables table to check. - Docker: the Docker documentation states that Docker routes container traffic in the
nattable, diverting packets before they reach theINPUTandOUTPUTchains used by ufw. A ban on the host will not necessarily stop traffic to a published container port.
Run a test ban from start to finish
The only convincing proof is a ban you can see in the firewall and then remove. Use an address from the documentation range 203.0.113.0/24 so you never ban a real host:
sudo fail2ban-client set sshd banip 203.0.113.9
sudo fail2ban-client get sshd banip --with-time
sudo nft list ruleset | grep 203.0.113.9
sudo iptables -S | grep 203.0.113.9
sudo tail -n 20 /var/log/fail2ban.log
sudo fail2ban-client set sshd unbanip 203.0.113.9
The client manual documents set JAIL banip and unbanip, and get JAIL banip --with-time for listing bans. Interpret the result:
- Listed by fail2ban and visible in the firewall: enforcement works. Your problem is on the detection side.
- Listed by fail2ban, absent from the firewall: the ban action is broken. Re-read the enforcement section, and look for action errors in the log.
- Command fails: the jail name is wrong or the jail is not running.
Then test detection with a real failure. From a second machine that is not in ignoreip, fail a few SSH logins using a throwaway account name, keep your admin session open in another terminal, and watch the counters:
sudo fail2ban-client status sshd
sudo tail -f /var/log/fail2ban.log
If you need more detail while debugging, sudo fail2ban-client set loglevel DEBUG raises verbosity (the client documents the level names, and the default in fail2ban.conf is INFO). Set it back with sudo fail2ban-client set loglevel INFO when you finish, and write anything permanent into the configuration files rather than relying on runtime changes. If you do lock yourself out while testing, our SSH locked out recovery guide covers the way back in.
It bans, but the attacks continue
Sometimes fail2ban is healthy and the noise in your logs does not shrink. That is expected when the attacker rotates addresses: a ban stops one IP, not the campaign. Each new address gets its own maxretry attempts before it is blocked, so a botnet of many sources keeps producing failures, as our setup guide notes. A rising Total banned with constant log noise means the tool works; it does not mean your login is safe.
The durable answer is to make guessed passwords worthless. Turn on key-only login so that no number of attempts can succeed, then let fail2ban handle what is left. The sshd_config(5) page lists PasswordAuthentication and KbdInteractiveAuthentication as defaulting to yes, so both need to be set to no explicitly:
# /etc/ssh/sshd_config.d/10-keys-only.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
Before you apply it, confirm your key login works from a second terminal, check the syntax with sudo sshd -t, and then read the effective values with sudo sshd -T, which the sshd manual describes as checking the configuration and printing the effective configuration. The full procedure is in our SSH hardening guide, and the broader plan for this attack pattern is in SSH brute force protection. The glossary entries on brute-force attacks and intrusion prevention explain where fail2ban sits in that stack.
Two more levers help once the basics hold. The shipped jail.conf documents bantime.increment as using the database to find previously banned IPs and lengthen their ban, and the setup guide covers the recidive jail for repeat offenders. Finally, alert on a jail that goes quiet: a jail that suddenly records no failures on an exposed server deserves a look, which is the monitoring habit described in our log analysis guide.
Frequently asked questions
Why is fail2ban running but not banning anyone?
Usually the jail cannot see failed logins or cannot enforce a ban. Check fail2ban-client status for your jail: zero failures points to the wrong log or journal or a filter mismatch, failures without bans point to thresholds or ignoreip, and bans without blocking point to the firewall action.
How do I check that fail2ban is reading the right log?
Run fail2ban-client get with your jail name and logpath, or journalmatch for the systemd backend, then open that source and confirm it contains recent SSH authentication lines. The status output for the jail also lists the monitored files.
Why does fail2ban-regex show missed lines for failed logins?
It means the filter does not recognise the log format, so the jail can never count those failures. Run it with --print-all-missed, compare the missed lines with the filter, check the filter mode, and look at the date format in the lines.
Why does fail2ban say an IP is banned but it can still connect?
The ban is recorded but not enforced. Check that the banaction matches your firewall, that the rule is present in nft, iptables or ufw, that ufw is enabled, and that an existing open connection is not simply surviving the ban. IPv6 and Docker published ports are also common gaps.
Why do my own failed logins from the server never get banned?
The ignoreself option defaults to true, so the server's own addresses are never banned, and any address in ignoreip is exempt too. Test from a different host that is not listed in ignoreip.
Do I need to restart fail2ban after editing jail.local?
A change only applies after the configuration is re-read. Run fail2ban-client -t to check for errors, then fail2ban-client reload, which re-reads the configuration without restarting the server, and confirm with fail2ban-client status.
Why does the SSH jail show zero failures on Ubuntu or Debian?
The host may log sshd only to the journal while the jail reads a log file, or a custom journalmatch uses the wrong unit name, since Ubuntu names the service ssh.service. Set backend to systemd, check journalmatch, and verify with fail2ban-regex against systemd-journal.
Is fail2ban useless if attackers keep switching IP addresses?
No, but it cannot stop a campaign that rotates addresses, because each new address gets its own attempts. Pair it with key-only SSH login, so guessed passwords never succeed, and keep fail2ban for reducing noise and catching what remains.
Not sure where your server stands?
Get a free, read-only security check, or talk to a security engineer about managed protection.
Get your free server check Talk to a security engineer