What it does
fail2ban reads the logs. When one address fails to log in maxretry times within findtime, it adds a firewall rule that blocks that address for bantime. That is all. It does not inspect SSH traffic and does not make a weak password strong.
Install
sudo apt install fail2ban python3-systemd
python3-systemd lets it read the systemd journal. On Red Hat family systems the package is in EPEL. On Debian 13 and Ubuntu 24.04 the package already enables the sshd jail, with the systemd backend and nftables, through /etc/fail2ban/jail.d/defaults-debian.conf.
Configure: jail.local
Do not edit jail.conf, because updates can replace it. Put your settings in /etc/fail2ban/jail.local:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.10
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
backend = systemd
| Setting | Meaning | Package default |
|---|---|---|
bantime | How long a ban lasts | 10m |
findtime | The window in which failures add up | 10m |
maxretry | Failures allowed in that window | 5 |
ignoreip | Addresses never banned | none |
backend | Where logs come from | auto |
Add your own fixed address to ignoreip, so a few typos cannot lock you out. If your address changes, make sure you have a console to fall back on.
Use backend = systemd where SSH logs go to the journal, as on current Debian, which may have no /var/log/auth.log at all. On a system that writes that file, you can use backend = auto and logpath instead.
If sshd listens on another port, add port = 2222 to the jail. The rule we saw matched only the port in the jail's setting (tcp dport 22), so a ban on the wrong port blocks nothing.
Test the file, then restart:
sudo fail2ban-client -t
sudo systemctl restart fail2ban
Check that it really works
sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 4
| `- Journal matches: _SYSTEMD_UNIT=ssh.service + _COMM=sshd
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: 203.0.113.20
Do one real test. From another address, not the server itself and not one in ignoreip, fail a password login and run the command again. Total failed must go up. If it stays at 0, the jail is not reading the right logs, and the rest does nothing. Compare the journal match with your unit name, ssh.service or sshd.service, and check with journalctl -u ssh -n 20.
Once banned, the client sees this:
ssh: connect to host 203.0.113.10 port 22: Connection refused
The firewall rule can be seen with sudo nft list ruleset:
table inet f2b-table {
set addr-set-sshd {
type ipv4_addr
elements = { 203.0.113.20 }
}
chain f2b-chain {
type filter hook input priority filter - 1; policy accept;
tcp dport 22 ip saddr @addr-set-sshd reject with icmp port-unreachable
}
}
Everyday commands
| Goal | Command |
|---|---|
| List jails | sudo fail2ban-client status |
| Unban one address | sudo fail2ban-client set sshd unbanip 203.0.113.20 |
| Unban everything | sudo fail2ban-client unban --all |
| Ban by hand | sudo fail2ban-client set sshd banip 203.0.113.20 |
| See bans and when they end | sudo fail2ban-client get sshd banip --with-time |
| Ignore an address now | sudo fail2ban-client set sshd addignoreip 203.0.113.10 |
| Read the settings in use | sudo fail2ban-client get sshd bantime |
| Read its log | sudo tail /var/log/fail2ban.log |
The addignoreip change lasts only until the next reload. Add it to jail.local to keep it.
What counts as a failure
We tested the default mode on OpenSSH 10.0 with these results:
- A wrong password counted, as did a wrong password for a user that does not exist.
- A Too many authentication failures disconnect counted as one.
- Connections offering a wrong key and then giving up did not count. They appear in the log as
Connection closed by authenticating user.
With mode = aggressive in the [sshd] jail, the wrong-key connections were counted as well. It catches more, and it also makes it easier to ban someone with a badly configured client, so keep ignoreip up to date.
What it does not protect against
- A stolen key or a leaked password. A correct login is not a failure, so there is no ban.
- Slow or distributed guessing. Many addresses making one or two attempts each stay below the limit.
- A weakness in sshd itself. It reacts to log lines, after the fact.
- Other services, unless they have their own jails.
It can also hurt you: everyone behind an office NAT shares one address, so one person's mistakes can lock out the whole office.
If a key leaks, the fix is to know where it is installed and remove the SSH key from every server.
What matters more
If the server accepts only keys, password guessing has nothing to guess, whatever the number of attempts. So do this first:
- Use a key and turn off password login.
- Limit who can reach port 22 with a firewall or a VPN, if you can.
- Review the rest of your sshd settings.
After that, fail2ban is a useful extra that keeps logs quieter. Also, OpenSSH 9.8 and later has its own PerSourcePenalties, on by default, which refuses misbehaving addresses for a short time. That is much shorter than a typical fail2ban ban, so the two work well side by side.
Questions
How do I unban an IP in fail2ban?
Run sudo fail2ban-client set sshd unbanip followed by the address. sudo fail2ban-client unban --all lifts every ban in every jail.
How do I check that fail2ban is working?
Run sudo fail2ban-client status sshd, fail a login from another address, and run it again. Total failed must go up. If it stays at 0, the jail is not reading the SSH logs.
Why does a banned client see Connection refused?
With the default nftables action on Debian, a banned address gets a reject, so ssh prints Connection refused rather than a timeout. An action that drops packets would look like a timeout instead.
Do wrong SSH keys count as failures?
Not in the default mode. In our test, five connections with a wrong key added nothing to the count. mode = aggressive in the jail did count them.