# fail2ban not working: not banning or not blocking

> fail2ban not working? Find out if the jail fails to ban or bans without blocking, then fix the log source, filter, ignoreip, thresholds, port or firewall.
>
> Source: https://fail2ban.dev/troubleshooting/not-banning/ · Updated 2026-10-25 · Checked against fail2ban 1.1.1
> fail2ban.dev is an independent guide, not the official fail2ban project.

When fail2ban is not working, it is either not banning (the jail never sees the failures) or banning without blocking (the firewall rule doesn't cover the traffic). Run `sudo fail2ban-client status sshd`. If `Total failed` is 0 during attacks, fix the log source or filter. If the IP is in `Banned IP list` but still connects, check the jail's `port`, the banaction and Docker or proxy routing.

## Quick diagnosis: not banning, or banned but not blocked?

Start with fail2ban's own view of the jail:

```bash
sudo fail2ban-client status sshd
sudo fail2ban-client banned 203.0.113.42
```
$ sudo fail2ban-client banned 203.0.113.42
[['sshd']]
```

For Part 1, test the filter against the real log source. For Part 2, compare the firewall rule with how the traffic arrives:

```bash
sudo fail2ban-regex /var/log/auth.log sshd       # file-based jails
sudo fail2ban-regex systemd-journal sshd         # journal-based jails

# Part 2: is the ban in the firewall, and how does the traffic arrive?
sudo nft list ruleset | grep -B2 -A8 f2b
sudo iptables -S | grep -i f2b
sudo ufw status numbered | grep 203.0.113.42
sudo ss -tnp | grep 203.0.113.42
```
|---|---|
| Jail missing from `Jail list` | [Jail not enabled or not loaded](#the-jail-isnt-enabled) |
| `Total failed: 0`, log file exists | [Filter doesn't match](#the-filter-doesnt-match-your-log-lines) or [wrong log file](#fail2ban-is-reading-the-wrong-log) |
| `Total failed: 0`, `Journal matches:` shown | [Wrong journalmatch](#wrong-journalmatch-for-ssh) |
| `Total failed` rising, `Currently failed` stays low | [maxretry/findtime too lax](#maxretry-and-findtime-are-too-lax) |
| `Ignore … by …` lines in `/var/log/fail2ban.log` | [ignoreip or ignoreself](#ignoreip-or-ignoreself-covers-the-address) |
| `Found a match but no valid date/time` warning | [Timestamp problem](#timestamps-or-time-zone-dont-line-up) |
| Banned in fail2ban, address absent from the firewall | [banaction doesn't match your firewall](#the-banaction-doesnt-match-your-firewall), or the action errors |
| Rule present, but for port 22 while SSH listens on 2222 | [Wrong port](#the-jail-uses-the-wrong-port) |
| Rule present, `ss` shows the session still open | [Established connection](#the-connection-was-already-open) |
| Target is a Docker container | [Docker bypasses INPUT](#docker-containers-bypass-the-input-chain) |
| `ss` shows a Cloudflare or proxy address, not the banned one | [Proxy in front](#a-proxy-or-cdn-sits-in-front) |
| Attacker uses an IPv6 address | [IPv6 not covered](#ipv6-isnt-covered) |

## Part 1: the jail doesn't ban

In this case the jail usually isn't seeing the failures at all. It reads the wrong log file, the service logs to the systemd journal instead, or the filter doesn't fit your log format.

### The jail isn't enabled

Every jail in `jail.conf` ships with `enabled = false`. Only the jails you switch on in `jail.local` or `jail.d/*.local` run. On Debian and Ubuntu the package enables `sshd` for you in `/etc/fail2ban/jail.d/defaults-debian.conf`. On Fedora, RHEL, Rocky and AlmaLinux (EPEL) nothing is enabled until you do it.

**Symptom:** `sudo fail2ban-client status` doesn't list the jail, and `sudo fail2ban-client status nginx-http-auth` answers `Sorry but the jail 'nginx-http-auth' does not exist`.

**Fix:** enable the jail in `/etc/fail2ban/jail.local`. The section name must be exactly the jail name, in square brackets:

```ini
[sshd]
enabled = true

[nginx-http-auth]
enabled = true
```
sudo fail2ban-client -t && sudo fail2ban-client reload
```

### fail2ban is reading the wrong log

A file-based jail only sees what's written to its `logpath`. If the service writes somewhere else, fail2ban watches an empty or stale file and never counts a failure.

**Symptoms:** `File list:` in the jail status points to a file that doesn't grow when you fail a login. Or the service refuses to start with `Have not found any log file for sshd jail`. That one has [its own page](/troubleshooting/no-log-file/).

```bash
sudo tail -n 3 /var/log/auth.log        # is this file actually being written?
```

- **Debian 12 and other journal-only systems.** Debian 12 doesn't install rsyslog, so `/var/log/auth.log` doesn't exist and SSH failures only go to the systemd journal. Set `backend = systemd` for the jail (and install `python3-systemd`). Debian 13 and Ubuntu 24.04+ packages already do this for `sshd`.
- **Ubuntu 24.04: `backend = systemd` for every jail.** Ubuntu 24.04's `/etc/fail2ban/jail.d/defaults-debian.conf` sets `backend = systemd` under `[DEFAULT]`. Every jail you enable then reads the journal and **ignores its `logpath`**. That's fine for `sshd`, but nginx, Apache and most app jails log to files and never count a failure. Add `backend = auto` to each file-based jail. The jail status should then show `File list:` instead of `Journal matches:`.
- **RHEL-family systems** log SSH to `/var/log/secure`, not `auth.log`. The packaged `paths-fedora.conf` handles that, unless you hard-coded `logpath = /var/log/auth.log` from a Debian tutorial.
- **Web servers with per-site logs.** Nginx and Apache virtual hosts often log to `/var/log/nginx/example.com.error.log`. Point `logpath` at those files; globs like `/var/log/nginx/*error.log` work.
- **Apps in containers** log to stdout. fail2ban on the host can't see that unless the log is bind-mounted to a file or read from the journal (journald log driver). The [Docker guide](/guides/docker/) shows both.

The [backend page](/config/backend/) explains `auto`, `pyinotify`, `polling` and `systemd` in detail.

### The filter doesn't match your log lines

If the right file is being read but `Total failed` stays at 0, the filter's regex doesn't fit your log format. [fail2ban-regex](/fail2ban-regex/) tells you for certain:

```bash
sudo fail2ban-regex /var/log/nginx/error.log nginx-http-auth
```

- **Custom log format.** A changed nginx `log_format`, an app that logs JSON, or a syslog template that prepends extra fields. Write a `filter.d/<name>.local` with an adjusted `failregex`. The [custom filter guide](/filters/custom/) and the [regex tester](/tools/regex-tester/) help.
- **OpenSSH 9.8+ and an older filter.** Since OpenSSH 9.8 many messages come from a process called `sshd-session` instead of `sshd`. The upstream `sshd` filter matches both only from fail2ban 1.1.1. Debian 13 patched its 1.1.0 package. A hand-installed 1.1.0 or older needs an update.
- **SSH with keys only.** In the default `mode = normal`, bots that connect and disconnect without trying a password aren't counted as failures. If your log is full of `Connection closed by authenticating user … [preauth]`, set `mode = aggressive` for the jail. The [SSH guide](/guides/ssh/) covers all filter modes.

### Wrong journalmatch for SSH

With `backend = systemd` the jail doesn't read a file. It reads journal entries that match `journalmatch`. The upstream `sshd` filter uses `_SYSTEMD_UNIT=sshd.service + _COMM=sshd + _COMM=sshd-session` (the `+` means OR). On Debian and Ubuntu the SSH unit is called `ssh.service`, so a hand-written `journalmatch = _SYSTEMD_UNIT=sshd.service` matches nothing.

```bash
sudo fail2ban-client get sshd journalmatch
sudo journalctl _SYSTEMD_UNIT=ssh.service -n 5
```

```ini
[sshd]
enabled = true
backend = systemd
journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd + _COMM=sshd-session
```

fail2ban never bans addresses in `ignoreip`, and with `ignoreself = true` (the default) it never bans the server's own addresses. Testing from the server itself, from another container on the same host, or from a whitelisted LAN produces no ban, by design.

**Symptom:** lines like `[sshd] Ignore 192.168.1.20 by ip` in `/var/log/fail2ban.log`.

```bash
sudo fail2ban-client get sshd ignoreself
sudo grep "Ignore " /var/log/fail2ban.log | tail
```

### maxretry and findtime are too lax

The defaults are `maxretry = 5` within `findtime = 10m`. A slow bot that tries once every three minutes never reaches 5 failures in 10 minutes, so it's never banned. You'll see `Total failed` climbing while `Currently failed` hovers at 1 or 2.

**Fix:** widen the window and lower the threshold for internet-facing services:

```ini
[sshd]
enabled  = true
maxretry = 3
findtime = 1h
bantime  = 1h
```

### Timestamps or time zone don't line up

fail2ban only counts a failure if it can read the time from the log line and that time falls inside `findtime`. Two warnings in `/var/log/fail2ban.log` point here:

- `Found a match but no valid date/time found for '…'` means the line matched but fail2ban couldn't parse its date. Set a `datepattern` for the jail (see `man jail.conf`) or fix the application's log format.
- `Detected a log entry 2h before the current time in operation mode. This looks like a timezone problem.` means the log is written in a different time zone than the system. Set `logtimezone = UTC` (or the right offset) in the jail, or fix the app's time zone. Containers that log in UTC on a host set to local time hit this often.

Lines older than `findtime` are skipped when fail2ban starts and reads a log from the beginning. That's why replaying an old log file doesn't produce bans.

### The log shows the proxy's IP, not the attacker's

If your web app sits behind nginx, Traefik, Caddy, a load balancer or Cloudflare, its log may record the proxy's address for every request. fail2ban then either bans the proxy (if it isn't ignored) or nothing (if it is, for example `127.0.0.1` covered by `ignoreself`). Fix the logging first: [fail2ban bans the proxy IP](/troubleshooting/reverse-proxy-ip/).

### Host names instead of IPs (usedns)

Some services log a host name instead of an IP. With the default `usedns = warn`, fail2ban resolves it and logs a warning. With `usedns = no` those lines are ignored entirely. Prefer configuring the service to log IP addresses. For SSH, keep `UseDNS no` (the OpenSSH default) in `sshd_config`.

## Part 2: the IP is banned but still connects

fail2ban doesn't block anything itself. It runs the commands of the jail's [ban action](/config/banaction/), which add a firewall rule. If the IP is banned in fail2ban but still gets in, that rule doesn't apply to the traffic you care about.

### The jail uses the wrong port

The ban rule only applies to the ports set in the jail. The `sshd` jail uses `port = ssh`, which is port 22. If you moved SSH to 2222, the rule blocks 22 and the attacker keeps hitting 2222.

**Symptom:** the nftables rule reads `tcp dport 22` (or `ssh`) and the iptables rule shows `--dport 22`, but `ss -tlnp` shows sshd listening on a different port.

**Fix:** set the real port(s) in the jail. Several ports are comma-separated:

```ini
[sshd]
enabled = true
port    = 2222

[nginx-http-auth]
enabled = true
port    = http,https,8443
```
sudo fail2ban-client reload sshd
```

### The banaction doesn't match your firewall

If the action's commands target a firewall that isn't the one filtering your traffic, or fail outright, the ban exists only in fail2ban's memory. Check which action the jail uses and look for action errors:

```text
$ sudo fail2ban-client get sshd actions
The jail sshd has the following actions:
nftables
```
sudo grep -E "ERROR|returned [0-9]+|not found" /var/log/fail2ban.log | tail -20
```

- **iptables action on an nftables-only system.** Debian 12's fail2ban 1.0.2 defaults to `iptables-multiport`. A minimal install without the `iptables` package logs `iptables: not found` and the action returns 127. Install `iptables`, or switch to `banaction = nftables` and `banaction_allports = nftables[type=allports]`. See [config errors](/troubleshooting/config-errors/#nft-not-found-or-iptables-not-found).
- **firewalld manages the firewall.** Use the `firewallcmd-*` actions such as `firewallcmd-ipset` or `firewallcmd-rich-rules`, so bans live inside firewalld's own rules and survive `firewall-cmd --reload`. Fedora's `fail2ban-firewalld` package sets this up. See the [firewalld guide](/guides/firewalld/).
- **iptables-nft vs iptables-legacy.** Both backends' rules are evaluated by the kernel, so a ban in either one still drops packets. But `iptables -S` only shows rules from the backend it's linked to. Check `iptables -V` (it prints `nf_tables` or `legacy`) and look with the matching tool before you conclude the rule is missing.
- **A firewall restart wiped the rules.** `ufw reload`, a restart of `nftables.service` (Debian's `/etc/nftables.conf` starts with `flush ruleset`) or a custom firewall script removes fail2ban's rules. The upstream `fail2ban.service` is `PartOf=` the iptables, firewalld and nftables services, so fail2ban restarts with them and re-applies its bans. Custom scripts don't get that, so restart fail2ban after running them.

> **"fail2ban does not create chain"**
The `nftables` and `iptables` actions create their table and chain **at the first ban**, not when fail2ban starts. An empty `nft list ruleset` right after startup is normal. Ban a test address with `sudo fail2ban-client set sshd banip 192.0.2.15` and look again.

### The connection was already open

Whether a ban kills an open session depends on the action:

- **nftables** and **iptables** (fail2ban's chain is inserted at the top of `INPUT`) match every packet from the address, so established sessions stall too.
- **ufw** inserts its rule into ufw's user chains, which come after the "accept established connections" rule. Only **new** connections are blocked, so an attacker already connected keeps going. The ufw action's `kill-mode` option fixes this:

```ini
[DEFAULT]
banaction = ufw[kill-mode=ss]
```

### Docker containers bypass the INPUT chain

Traffic to a published Docker port is DNAT-ed and goes through the `FORWARD` path, not `INPUT`. Rules in `INPUT` or in nftables chains hooked on `input` never see it. Use `chain = DOCKER-USER` with an iptables action, or an nftables chain on the `forward` hook. The full walk-through is in the [fail2ban Docker guide](/guides/docker/).

### A proxy or CDN sits in front

If Cloudflare, a load balancer or a reverse proxy on another host terminates the connection, packets reaching your server come from the proxy's address. Banning the visitor's real IP in your local firewall does nothing, because that IP never connects to you directly. Ban at the edge instead (the `cloudflare-token` action, see the [Cloudflare guide](/guides/cloudflare/)) or in the proxy. See also [fail2ban bans the proxy IP](/troubleshooting/reverse-proxy-ip/).

### IPv6 isn't covered

fail2ban bans IPv6 addresses when the ban action supports them. `nftables` and `iptables` (via `ip6tables`) do. Problems appear when:

- `ip6tables` isn't installed, so IPv6 bans fail with an action error.
- ufw has `IPV6=no` in `/etc/default/ufw`.
- A custom action only handles IPv4.

Check for IPv6 entries with `sudo fail2ban-client get sshd banip`, and make sure the action shows an IPv6 set (`addr6-set-sshd` for nftables) or ip6tables rule after an IPv6 ban.

### An ACCEPT rule comes before fail2ban's rule

In iptables, the first matching rule wins. fail2ban inserts its jump at position 1 of the chain when the action starts, which is at the first ban. Rules inserted later with `-I`, such as a script that adds `-I INPUT -p tcp --dport 22 -j ACCEPT` or a firewall that rebuilds the chain, can end up above it. `sudo iptables -L INPUT -n --line-numbers` shows the order. The `f2b-sshd` jump must come before any ACCEPT for the same port.

In nftables this can't happen across tables. fail2ban's chain runs at priority `-1` on the `input` hook, and its `reject` is final no matter what other chains accept later.

## Confirm the fix

For a Part 1 fix, trigger failures from an address that isn't ignored and watch fail2ban react:

```bash
```
2026-10-25 18:36:10,238 fail2ban.filter         [1997]: INFO    [sshd] Found 203.0.113.42 - 2026-10-25 18:36:10
2026-10-25 18:36:11,027 fail2ban.actions        [1997]: NOTICE  [sshd] Ban 203.0.113.42
```

For a Part 2 fix, ban a test address you control, such as a VPS or a phone hotspot, check the rule and try to connect:

```bash
sudo nft list set inet f2b-table addr-set-sshd    # nftables
sudo iptables -L f2b-sshd -n                      # iptables
```

## FAQ

### Why is fail2ban not banning SSH attempts?

Usually the `sshd` jail reads `/var/log/auth.log` on a system that logs only to the systemd journal, or the filter doesn't count key-only probes. Set `backend = systemd` for `[sshd]` if `auth.log` doesn't exist, and use `mode = aggressive` to count pre-auth disconnects. Check `Total failed` in `fail2ban-client status sshd`.

### Why does fail2ban show failures but never ban?

Failures are counted per IP within `findtime`. If an IP doesn't reach `maxretry` failures inside that window, it's never banned. Slow bots exploit this. Raise `findtime` to an hour or more and lower `maxretry`. Also check the IP isn't covered by `ignoreip` or `ignoreself`.

### Why does fail2ban ban an IP but it can still connect?

The firewall rule doesn't apply to that traffic. The jail's `port` may not match the real service port, the banaction may target a firewall that isn't filtering your traffic, the traffic may go to Docker through FORWARD, or a proxy or CDN may connect on the visitor's behalf. Compare the firewall rule with `ss -tn` output.

### Why is fail2ban not creating iptables rules?

fail2ban creates its iptables chain only at the first ban, so an empty ruleset after startup is normal. If rules still don't appear after a ban, the jail probably uses an nftables or ufw action, or the iptables command failed. Check `fail2ban-client get <jail> actions` and grep `/var/log/fail2ban.log` for `ERROR`.

### Does fail2ban close existing connections?

With the nftables and iptables actions, yes in practice: every packet from the banned address is rejected, so open sessions stall. With ufw and firewalld rich rules, established connections are accepted before the ban rule. Use `banaction = ufw[kill-mode=ss]` to kill them at ban time.

### How do I test whether fail2ban is working?

Fail a few logins from an outside address, such as a phone on mobile data, and watch `/var/log/fail2ban.log` for `Found` and `Ban` lines. To test the firewall side, ban a test address by hand with `sudo fail2ban-client set sshd banip 192.0.2.15`, check the rule, then unban it.
