How to set up fail2ban for SSH

Install fail2ban, create /etc/fail2ban/jail.local with an enabled [sshd] jail and backend = systemd, restart it, and check with fail2ban-client status sshd. It bans addresses after repeated failed logins. It reduces noise and slows guessing, but turning off password login protects you far more.

Updated . Run with fail2ban 1.1.0 on a throwaway Debian 13 container booted with systemd and using nftables, with OpenSSH 10.0, and real failed logins from a second container. The default jail file was also read on Ubuntu 24.04.

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
SettingMeaningPackage default
bantimeHow long a ban lasts10m
findtimeThe window in which failures add up10m
maxretryFailures allowed in that window5
ignoreipAddresses never bannednone
backendWhere logs come fromauto

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

GoalCommand
List jailssudo fail2ban-client status
Unban one addresssudo fail2ban-client set sshd unbanip 203.0.113.20
Unban everythingsudo fail2ban-client unban --all
Ban by handsudo fail2ban-client set sshd banip 203.0.113.20
See bans and when they endsudo fail2ban-client get sshd banip --with-time
Ignore an address nowsudo fail2ban-client set sshd addignoreip 203.0.113.10
Read the settings in usesudo fail2ban-client get sshd bantime
Read its logsudo 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:

  1. Use a key and turn off password login.
  2. Limit who can reach port 22 with a firewall or a VPN, if you can.
  3. 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.

Sources

More in Securing a server

All guides