# Protect SSH with fail2ban

> Protect SSH with fail2ban: a working sshd jail, systemd backend, custom SSH port, aggressive mode for key-only servers, sshd-session on OpenSSH 9.8+ and tests.
>
> Source: https://fail2ban.dev/guides/ssh/ · Updated 2026-10-25 · Checked against fail2ban 1.1.1
> fail2ban.dev is an independent guide, not the official fail2ban project.

To protect SSH with fail2ban, enable the `sshd` jail in `/etc/fail2ban/jail.local` with `enabled = true`, set `port` to the port sshd really listens on (for example `port = 2222`), use `backend = systemd` on distributions without `/var/log/auth.log`, and run `sudo fail2ban-client reload`. Check it with `sudo fail2ban-client status sshd`. The defaults ban an IP for 10 minutes after 5 failures in 10 minutes.

```ini
[sshd]
enabled  = true
port     = ssh
backend  = systemd
maxretry = 5
findtime = 10m
bantime  = 1h
```
sudo fail2ban-client reload
sudo fail2ban-client status sshd
```

## The stock sshd jail

fail2ban ships an `[sshd]` jail in `/etc/fail2ban/jail.conf`. Upstream 1.1.1 defines it as:

```ini
[sshd]
#mode   = normal
port    = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
```

Most packages already enable the jail. Check what yours does before adding your own:

```bash
ls /etc/fail2ban/jail.d/                        # Fedora, EPEL: 00-firewalld.conf, 00-systemd.conf
```
|---|---|---|
| Debian 12 | 1.0.2 | `[sshd] enabled = true` only, but there's no `/var/log/auth.log` (no rsyslog) |
| Debian 13 | 1.1.0 | nftables, `backend = systemd`, `journalmatch` for `ssh.service`, enabled |
| Ubuntu 24.04 | 1.0.2 | nftables, `backend = systemd` for **all** jails, `[sshd] enabled = true` |
| Fedora, RHEL 9/10 (EPEL) | 1.1.0 | firewalld actions; `backend = systemd` with the fail2ban-systemd package |

## Choose the right backend: log file or systemd journal

fail2ban has to read sshd's log messages from somewhere. If it looks in the wrong place, the jail starts but never sees a failure — the most common reason for "fail2ban ssh not working".

- **Log file** (`backend = auto`): reads `/var/log/auth.log` or `/var/log/secure`. Needs rsyslog writing that file.
- **Journal** (`backend = systemd`): reads the systemd journal directly. Needs the Python systemd bindings (`python3-systemd` on Debian/Ubuntu, `python3-systemd` on Fedora/EPEL).

On **Debian 12** there is no rsyslog by default, so `/var/log/auth.log` doesn't exist and fail2ban 1.0.2 refuses to start the jail with `Have not found any log file for sshd jail`. Fix it with the journal:

```bash
```
[sshd]
enabled = true
backend = systemd
```

```text
Journal matches:	_SYSTEMD_UNIT=sshd.service + _COMM=sshd + _COMM=sshd-session
```

> **Ubuntu 24.04 sets backend = systemd for every jail**
Ubuntu 24.04's `defaults-debian.conf` puts `backend = systemd` in `[DEFAULT]`. That's right for sshd, but any jail that reads a log file (nginx, Apache, Nextcloud…) must set `backend = auto` itself, or fail2ban silently ignores its `logpath`.

## fail2ban SSH on a custom port

If sshd listens on a non-standard port, tell the jail. `port = ssh` means port 22, so fail2ban would block port 22 while the attacker keeps hammering 2222:

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

```ini
```

```bash
```

The [sshd filter](/guides/ssh/#filter-sshd) has four modes, selected with `mode` in the jail:

```ini
[sshd]
enabled = true
mode    = aggressive
```
|---|---|---|
| `normal` (default) | — wrong passwords, invalid users, AllowUsers/DenyUsers refusals | `Failed password for invalid user admin from 203.0.113.42` |
| `ddos` | Connections that never authenticate, protocol garbage, pre-auth disconnects | `Timeout before authentication for 203.0.113.42 port 51234` |
| `extra` | Clients with no usable auth method or no common algorithms | `Unable to negotiate with 203.0.113.42 port 51234: no matching host key type found.` |
| `aggressive` | `ddos` + `extra` combined | all of the above |

We ran these lines through `fail2ban-regex` with each mode: the `Timeout before authentication` line only matches in `ddos`/`aggressive`, and the `Unable to negotiate` line only in `extra`/`aggressive`.

### Key-only SSH needs aggressive mode

If you've set `PasswordAuthentication no`, bots can't produce `Failed password` lines any more. Their attempts end up as pre-auth disconnects instead:

```log
Oct 25 18:36:11 web1 sshd-session[1234]: Received disconnect from 203.0.113.42 port 51234:14: No supported authentication methods available [preauth]
```

## sshd-session: OpenSSH 9.8 and newer

Since OpenSSH 9.8 most authentication messages come from a process called `sshd-session`, not `sshd`. fail2ban's sshd filter only accepts that name from version 1.1.1 (`_daemon = sshd(?:-session)?`). Debian 13 patched its 1.1.0 package the same way. An older filter running against a newer OpenSSH misses every line — the jail looks healthy and bans nothing.

Check your filter:

```bash
```

```ini
[DEFAULT]
_daemon = sshd(?:-session)?
```

## Invalid users and preauth messages

The default `normal` mode already counts the lines bots generate most:

```log
Oct 25 18:36:12 web1 sshd[1234]: Failed password for invalid user admin from 203.0.113.42 port 51234 ssh2
```

## IPv6

fail2ban has handled IPv6 since 0.10, and the sshd filter matches IPv6 addresses like any other. The nftables action keeps a separate set for IPv6 bans, so nothing extra is needed:

```log
```

## A complete jail.local for SSH

```ini
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
bantime  = 1h
findtime = 10m
maxretry = 5
bantime.increment = true
bantime.maxtime   = 1w

[sshd]
enabled  = true
port     = 2222
mode     = aggressive
backend  = systemd
maxretry = 3

[recidive]
enabled = true
```

## Test the jail

**1. Test the filter against real log lines.** Pass the mode you configured:

```bash
sudo fail2ban-regex /var/log/auth.log 'sshd[mode=aggressive]'

# journal backend
sudo fail2ban-regex systemd-journal 'sshd[mode=aggressive]'
```

**2. Check the running jail:**

```text
$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed:	2
|  |- Total failed:	6
|  `- File list:	/var/log/auth.log
`- Actions
   |- Currently banned:	1
   |- Total banned:	1
   `- Banned IP list:	203.0.113.42
```

```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
```

## Why fail2ban SSH is not working

| Symptom | Likely cause | Fix |
|---|---|---|
| `Have not found any log file for sshd jail` | No `/var/log/auth.log` (Debian 12, minimal images) | `backend = systemd` + `python3-systemd` |
| `Total failed: 0` despite attacks | Old filter vs OpenSSH 9.8+ `sshd-session`, or wrong backend | `sshd.local` override above; check `fail2ban-regex` |
| Failures counted, nobody banned | Key-only server in `normal` mode | `mode = aggressive` |
| IP banned but still connecting | Ban on port 22, sshd on 2222 | Set `port` to the real port |
| Your own IP keeps getting banned | Saved wrong key/password in a client, monitoring | Add it to `ignoreip` |

The general checklist is in [fail2ban not banning](/troubleshooting/not-banning/).

## sshd, dropbear and selinux-ssh filter reference

The `dropbear` filter is for the small SSH server used on routers and embedded systems; the OpenWrt setup, including the log file it needs, is in [fail2ban on OpenWrt](/guides/openwrt/).

{{component:filter-ref:sshd.dropbear.selinux-ssh}}

## FAQ

### How do I enable fail2ban for SSH?

Create `/etc/fail2ban/jail.local` with an `[sshd]` section containing `enabled = true`, plus `backend = systemd` if your system has no `/var/log/auth.log`. Run `sudo fail2ban-client reload` and confirm with `sudo fail2ban-client status sshd`. Debian and Ubuntu packages already enable the sshd jail by default.

### How do I use fail2ban with a custom SSH port?

Set `port` in the sshd jail to the port sshd listens on, for example `port = 2222`, or `port = ssh,2222` for several ports. Detection works on any port, but the ban only blocks the ports listed, so leaving `port = ssh` lets attackers continue on 2222.

### What does mode = aggressive do in the sshd jail?

It combines the `ddos` and `extra` modes with the normal rules. Besides wrong passwords and invalid users it counts pre-auth disconnects, connections that never authenticate, and clients without a usable auth method or algorithm. Use it on servers that only accept SSH keys, where bots never produce "Failed password" lines.

### Do I need fail2ban if I only use SSH key authentication?

Keys stop password guessing, so fail2ban is no longer what keeps you safe. It still cuts log noise, saves resources and slows scanners down, and with `mode = aggressive` it bans clients that keep connecting without valid keys. Many admins run both: keys for security, fail2ban for hygiene.

### Should I use sshd or ssh as the jail name?

The jail is called `sshd` in fail2ban 0.10 and later, so use `[sshd]` in `jail.local` and `fail2ban-client status sshd`. Old tutorials use `[ssh]`; that section doesn't exist in current `jail.conf`, so settings placed under it have no effect.
