DEV Community

Cover image for Setting up Fail2ban: block brute-force attacks automatically
serverkueche.de
serverkueche.de

Posted on Originally published at serverkueche.de

Setting up Fail2ban: block brute-force attacks automatically

After a few days on the internet, take a look at journalctl -u ssh: hundreds of login attempts from foreign IPs, one every second. As long as your SSH is switched to key login, none of them gets in – but it clutters the log and eats resources. Fail2ban reads along with these logs and bans automatically whoever tries too often without success.

What are we building?

By the end, Fail2ban 1.1 on your Debian 13 monitors the SSH logins and, after too many failed attempts, bans the attacking IP for a defined time – via a firewall rule. Your own IP is on a whitelist so this never hits you. You can view bans, set them manually and lift them again.

Prerequisites

Step by step

Before we configure, three terms that explain the whole tool:

  • Filter – a pattern that detects a "failed login" in the log (for SSH, Fail2ban brings this ready-made).
  • Jail – connects a filter with a log source and the rules "how often in what time frame". The sshd jail monitors the SSH logins.
  • Action – what happens when it's exceeded: by default a firewall rule that blocks the IP for bantime.

In short: filter detects failed attempts → jail counts them → action bans. Everything you configure below is one of these three knobs.

Step 1: Install Fail2ban

sudo apt update
sudo apt install -y fail2ban
Enter fullscreen mode Exit fullscreen mode

Check the version:

fail2ban-client --version
Enter fullscreen mode Exit fullscreen mode
Fail2Ban v1.1.0
Enter fullscreen mode Exit fullscreen mode

⚠️ Debian starts banning right away – without your whitelist

The Debian package starts and enables the service during installation, and /etc/fail2ban/jail.d/defaults-debian.conf switches the sshd jail on right there. So Fail2ban bans from this moment on – with the defaults from jail.conf (10 minute ban after 5 failed attempts within 10 minutes) and without your own IP on the whitelist. So continue with step 2 immediately.

Step 2: Your own configuration in jail.local

⚠️ Never edit jail.conf

The shipped /etc/fail2ban/jail.conf is overwritten on updates. Your own settings always belong in /etc/fail2ban/jail.local – this file is preserved and overrides the defaults.

Create the file:

sudo nano /etc/fail2ban/jail.local
Enter fullscreen mode Exit fullscreen mode
[DEFAULT]
# How long the ban lasts
bantime = 1h
# Time window in which the failed attempts count
findtime = 10m
# This many failed attempts are allowed, then a ban
maxretry = 5
# Your own IP(s) – will NEVER be banned
ignoreip = 127.0.0.1/8 ::1 YOUR_OWN_IP

[sshd]
enabled = true
Enter fullscreen mode Exit fullscreen mode

Line by line:

  • bantime = 1h – ban duration. For stubborn cases, see the escalation in step 5.
  • findtime + maxretry – "5 failed attempts within 10 minutes → ban".
  • ignoreip – the most important line: enter your own IP here so you don't lock yourself out. If you have a changing IP at home, better use access via a fixed point (a VPN later) instead of a broad allowance.
  • [sshd] enabled = true – activates the SSH jail. On Debian it is already on via defaults-debian.conf; having it in your own file does no harm and makes visible what is running.

⚠️ Enter your own IP first

Set your own IP in ignoreip before you apply the configuration in step 3 – otherwise even your own typo on login could lock you out. The address you're currently connected from is shown by the first field of echo "$SSH_CLIENT".

💡 Debian 13 reads the systemd journal

On Debian 13, Fail2ban uses the systemd journal as its source by default – you don't need to specify a logpath (like /var/log/auth.log). On modern systems that file often no longer exists at all; the journal is the right source and works without extra configuration.

Step 3: Apply the configuration

First check that the file is syntactically correct – that saves you a service that won't come back up after the reload:

sudo fail2ban-client -t
Enter fullscreen mode Exit fullscreen mode
OK: configuration test is successful
Enter fullscreen mode Exit fullscreen mode

Now apply the new configuration:

sudo systemctl reload fail2ban
Enter fullscreen mode Exit fullscreen mode

The reload is the decisive step. The service has been running since installation – a systemctl start (or enable --now) does nothing at all on an already running service, your jail.local would stay ignored and Debian's defaults would still apply. So after every change to jail.local, run reload again.

That Fail2ban comes back on its own after a reboot has already been set up by the package – checking costs nothing:

systemctl is-enabled fail2ban
Enter fullscreen mode Exit fullscreen mode
enabled
Enter fullscreen mode Exit fullscreen mode

If this says disabled, catch up with sudo systemctl enable --now fail2ban. And a look at the service itself:

sudo systemctl status fail2ban
Enter fullscreen mode Exit fullscreen mode

You should see active (running).

Step 4: Check the status

This is how you see which jails are active:

sudo fail2ban-client status
Enter fullscreen mode Exit fullscreen mode
Status
|- Number of jail:  1
`- Jail list:   sshd
Enter fullscreen mode Exit fullscreen mode

And the details of the SSH jail:

sudo fail2ban-client status sshd
Enter fullscreen mode Exit fullscreen mode
Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  |- Total failed: 0
|  `- Journal matches:  _SYSTEMD_UNIT=ssh.service + _COMM=sshd
`- Actions
   |- Currently banned: 0
   |- Total banned: 0
   `- Banned IP list:
Enter fullscreen mode Exit fullscreen mode

The Journal matches line confirms that Fail2ban reads the systemd journal. Currently banned rises as soon as someone gets it wrong too often – and it adds up: on our test server, Fail2ban counted 9,494 failed attempts and 481 bans over eleven days of runtime – around 860 login attempts and 44 bans per day, without us having to do anything for it.

To see whether your values really apply and not Debian's defaults any more, ask directly:

sudo fail2ban-client get sshd bantime
Enter fullscreen mode Exit fullscreen mode
3600
Enter fullscreen mode Exit fullscreen mode

3600 seconds is the hour from your jail.local. If it still says 600, the reload from step 3 is missing – Fail2ban then keeps running with the defaults.

Step 5: Manage bans – and escalate them

Manually unban an IP (e.g. when a colleague mistyped):

sudo fail2ban-client set sshd unbanip 203.0.113.45
Enter fullscreen mode Exit fullscreen mode

Ban an IP immediately:

sudo fail2ban-client set sshd banip 203.0.113.45
Enter fullscreen mode Exit fullscreen mode

Both commands answer with the number of IPs they actually touched:

1
Enter fullscreen mode Exit fullscreen mode

If a 0 comes back, nothing happened – when unbanning, that usually means the IP wasn't banned at all (a typo in the address).

Against especially stubborn attackers, an escalating ban time pays off: whoever comes back is banned for longer. Add to the [DEFAULT] block:

bantime.increment = true
bantime.maxtime = 1w
Enter fullscreen mode Exit fullscreen mode

With that, Fail2ban doubles the ban time each time the same IP reoffends – up to a maximum of one week. After the change, sudo systemctl reload fail2ban. That the escalation is really active is confirmed – as in step 4 – by a direct query:

sudo fail2ban-client get sshd bantime.increment
Enter fullscreen mode Exit fullscreen mode
True
Enter fullscreen mode Exit fullscreen mode

When things go wrong

You locked yourself out. ignoreip was missing or contained the wrong IP. Connect via the console in the netcup SCP (independent of SSH) and unban yourself with sudo fail2ban-client set sshd unbanip YOUR_OWN_IP. Then enter your IP in ignoreip and reload. That's why the whitelist comes first in step 2.

fail2ban.service won't start (systemctl status shows "failed"). Almost always a typo in jail.local. Check the syntax with sudo fail2ban-client -t (test mode) – the command names the faulty line.

Your values from jail.local don't take effect. Bans only last 10 minutes, or your own IP gets thrown out despite ignoreip. Almost always the reload is missing – the service was already running before you wrote your first own line, and start/enable --now does nothing on a running service. What actually applies is shown by sudo fail2ban-client get sshd bantime (600 = Debian default, 3600 = your hour) and sudo fail2ban-client get sshd ignoreip; it is applied with sudo systemctl reload fail2ban.

No one is ever banned, even though the log is full of failed attempts. Check with sudo fail2ban-client status sshd whether Total failed rises at all. If it stays at 0, the filter isn't finding the entries – usually because an outdated guide set a logpath to a non-existent file. On Debian 13, remove the logpath from jail.local and use the journal (step 2).

Bans "don't work" – the IP keeps connecting. On Debian 13, Fail2ban bans via nftables (banaction = nftables from defaults-debian.conf). You can see it with sudo nft list table inet f2b-table: it holds the chain f2b-chain and inside it an address set addr-set-sshd with the banned IPs. sudo iptables -L -n | grep f2b, on the other hand, returns nothing – that's not a fault but the wrong tool layer, where older guides get stuck. If the table is missing entirely, the interplay with the firewall is stuck – restart the service and look at /var/log/fail2ban.log.

Maintenance & backups

  • Keep an eye on bans: sudo fail2ban-client status sshd shows at any time how many IPs are currently banned. The history is not in the journal but in a file of its own – per logtarget in /etc/fail2ban/fail2ban.conf, Fail2ban logs to /var/log/fail2ban.log:
  sudo grep -E "Ban|Unban" /var/log/fail2ban.log | tail -5
Enter fullscreen mode Exit fullscreen mode
  2026-08-16 01:36:52,583 fail2ban.actions        [326875]: NOTICE  [sshd] Ban 203.0.113.45
Enter fullscreen mode Exit fullscreen mode

journalctl -u fail2ban, by contrast, only shows the service's start, reloads and errors.

  • More jails: as soon as services with their own login are added (e.g. a web app), you can activate matching jails – following the same pattern as [sshd].
  • Back up jail.local: your configuration is quickly restored but belongs in the backup of your server configuration.
  • How this differs from CrowdSec: Fail2ban protects your host based on your logs. The modern successor CrowdSec adds collaborative intrusion prevention (shared block lists) to this and evaluates the access logs of your reverse proxy – worthwhile as soon as web apps come into play. To get started, Fail2ban is exactly right.

This post first appeared on serverkueche.de.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.