linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Simulator
  • Knowledge base
  • Interview prep
Index
Categories
All entries
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
home/linux/kb/Security/fail2ban

kb/security ── Security ── intermediate

fail2ban: automatic bans from logs

fail2ban reads logs (sshd, nginx, postfix), uses a regex to catch N failed attempts in a window, and adds the IP to firewall rules for bantime. It is the main tool against SSH brute-force.

view as markdownaka: fail-2-ban, fail2ban-client, jail-local

What it does and why

On any public SSH host, port 22 receives thousands of brute-force attempts per day: dictionary guesses like root/password, admin/admin, etc. A strong password plus keys protect you, but the logs fill up with noise and CPU is wasted on bcrypt checks.

fail2ban works like this:

/var/log/auth.log
     ↓ tail -F
fail2ban reads line by line
     ↓
applies a regex filter (filter.d/sshd.conf)
     ↓
if IP X has made N FAILs within findtime seconds
     ↓
→ add a REJECT rule for X in the firewall for bantime seconds
     ↓
when it expires, remove the rule

It applies to anything that writes logs: SSH, Apache/nginx (auth_basic), Postfix (smtp-auth), Dovecot, ProFTPD, Asterisk, named (DNS amplification).

Installation

bash
# Debian/Ubuntu
sudo apt install fail2ban
# RHEL/Fedora needs EPEL
sudo dnf install epel-release
sudo dnf install fail2ban

The daemon is fail2ban (via systemd: fail2ban.service). The CLI is fail2ban-client.

Config architecture

/etc/fail2ban/
├── jail.conf          ← vendor, DO NOT TOUCH (overwritten on apt upgrade)
├── jail.local         ← your override (create it yourself)
├── jail.d/            ← modular overrides (50-sshd.local and so on)
├── filter.d/          ← regex filters per service (sshd.conf, apache-auth.conf)
├── action.d/          ← actions (firewallcmd-ipset, iptables, ufw)
└── fail2ban.local     ← global daemon settings

The standard path: copy jail.conf to jail.local and edit local. fail2ban reads both, and local overrides vendor.

bash
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

Minimal jail.local

ini
[DEFAULT]
# Global settings for all jails
bantime  = 1h           # how long to ban (supports s, m, h, d, w)
findtime = 10m          # window for counting attempts
maxretry = 5            # attempt threshold
backend  = systemd      # read the journal instead of files (important!)
ignoreip = 127.0.0.1/8 ::1 192.168.0.0/24    # whitelist
banaction = iptables-multiport               # or firewallcmd-ipset, nftables-multiport
# SSH is almost always the first thing we enable
[sshd]
enabled = true
port    = ssh
filter  = sshd
logpath = %(sshd_log)s
maxretry = 3              # stricter than the global value for SSH
bantime  = 24h            # and ban for longer

Without enabled = true, a jail is not activated even if it is mentioned.

The options %(sshd_log)s and %(sshd_backend)s are variables from paths-*.conf; they automatically substitute the correct path for your distribution.

bantime / findtime / maxretry

Semantics:

  • maxretry is how many failures are enough for a ban.
  • findtime is the window in seconds within which maxretry is counted.
  • bantime is how long to ban.

Example: findtime=600 maxretry=5 → 5 failures in 10 minutes triggers a ban.

Too strict (maxretry=2) and you ban yourself on a typo. Too lenient (maxretry=20) and bots have time to guess. A reasonable SSH default is maxretry=3, bantime=24h, findtime=10m.

bantime.increment: exponential ban

Modern fail2ban can grow bantime for repeat offenders:

ini
[DEFAULT]
bantime           = 1h
bantime.increment = true        # enable
bantime.factor    = 2           # multiplier
bantime.maxtime   = 1w          # ceiling

First ban 1h, second 2h, fourth 8h, ..., capped at a week. After this, bots give up.

Which backend to choose

  • auto lets fail2ban decide for itself (often works poorly).
  • pyinotify watches a file via inotify (fast, for logs in a file).
  • systemd reads journald directly. It is recommended for modern distributions where sshd writes to the journal rather than to auth.log.

If the backend is wrong, fail2ban will start but see nothing. The symptom: fail2ban-client status sshd shows Total failed: 0 even though the journal has a million FAILs.

Activating specific jails

By default (jail.conf), most jails have enabled = false. Enable only the ones you need.

The most popular ones:

ini
[sshd]
enabled = true
...
[apache-auth]
enabled = true
port    = http,https
logpath = /var/log/apache2/error.log
[postfix]
enabled = true
port    = smtp,ssmtp,submission
logpath = %(postfix_log)s
[recidive]
enabled  = true                    # meta-jail: bans those already banned in other jails
bantime  = 1w
findtime = 1d
maxretry = 5

recidive is a cross-jail repeat offender. Someone gets banned in sshd, then in apache-auth, and recidive bans them for a week on top of the regular ban.

Managing with fail2ban-client

bash
# overview of all active jails
sudo fail2ban-client status

▸Status

▸|- Number of jail: 2

▸`- Jail list: sshd, recidive

# details for a specific jail
sudo fail2ban-client status sshd

▸Status for the jail: sshd

▸|- Filter

▸| |- Currently failed: 3

▸| |- Total failed: 1247

▸| `- File list: /var/log/auth.log

▸`- Actions

▸|- Currently banned: 5

▸|- Total banned: 143

▸`- Banned IP list: 45.x.x.x 196.x.x.x ...

# manual ban / unban
sudo fail2ban-client set sshd banip 1.2.3.4
sudo fail2ban-client set sshd unbanip 1.2.3.4
# lift all bans in all jails
sudo fail2ban-client unban --all
# reread the config without a restart
sudo fail2ban-client reload
sudo fail2ban-client reload sshd       # a single jail

How to write your own filter

Suppose you have your own API that logs [AUTH-FAIL] from 1.2.3.4 invalid token. Create /etc/fail2ban/filter.d/myapi.conf:

ini
[Definition]
failregex = ^.*\[AUTH-FAIL\] from <HOST> .*$
ignoreregex =

<HOST> is a special fail2ban token for substituting an IP/hostname.

Wire up the jail in jail.local:

ini
[myapi]
enabled = true
filter  = myapi
port    = http,https
logpath = /var/log/myapi/access.log
maxretry = 3

Check the regex without a restart:

bash
fail2ban-regex /var/log/myapi/access.log /etc/fail2ban/filter.d/myapi.conf

▸Lines: 1234 lines, 0 ignored, 17 matched, 1217 missed

If matched: 0, the regex does not work.

Debugging: why it does not ban

bash
sudo systemctl status fail2ban
sudo journalctl -u fail2ban -f                  # stream the daemon's logs
sudo tail -F /var/log/fail2ban.log              # its own log
# check that the jail sees its log:
sudo fail2ban-client status sshd
# Total failed should grow during attempts
# test the regex against a real file
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf

Common reasons for "it does not work":

  • Backend mismatch: sshd writes to the journal, but the jail is configured for a file. Set backend = systemd.
  • maxretry too high: you ban yourself during tests before the bots accumulate enough. Set it to 2 temporarily.
  • ignoreip covers the attacker: check whether their subnet is included.
  • the firewall did not apply the rule: on nftables systems with iptables-legacy there can be a conflict. Use banaction = nftables-multiport.

Comparison with alternatives

  • CrowdSec is a modern replacement that shares blocklists across all installations (federated). It is more complex but more effective against botnets.
  • sshguard is lightweight, only for SSH/SMTP/etc, written in C rather than Python.
  • firewalld + ipset by hand means you write the parsing script yourself. Do not do this in production.

§ команды

bash
sudo fail2ban-client status sshd

How many IPs are banned now in the SSH jail and how many there were in total: the main overview

bash
sudo fail2ban-client set sshd unbanip 1.2.3.4

Unban a specific IP, for when you accidentally caught your own ban

bash
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf

Test a filter regex against a real log to see how many lines match

bash
sudo fail2ban-client reload sshd

Apply jail.local changes without a full daemon restart

bash
sudo journalctl -u fail2ban -f

Live stream of fail2ban logs to watch bans/unbans in real time

§ см. также

  • sshSSH: Secure ShellSSH is an encrypted channel to a remote host: shell, file copy, port-forwarding. Standard port 22, authentication by keys or password.
  • apache-httpdApache httpd: the web serverApache httpd is a web server. On RHEL the package is `httpd`, config `/etc/httpd/conf/httpd.conf`. On Debian/Ubuntu the package is `apache2`, config `/etc/apache2/apache2.conf` + `sites-enabled/`.
  • bind-dns-serverBIND: Authoritative and Caching DNS ServerBIND (Berkeley Internet Name Domain) is the most widely deployed DNS server on Linux. The daemon is `named`, the config is `/etc/named.conf` or `/etc/bind/named.conf`, and control goes through `rndc`.
  • pamPAM: Pluggable Authentication ModulesPAM is the authentication framework in Linux. Programs (sudo, login, sshd) do not check passwords themselves. They call PAM, which decides whether to let you in through a stack of modules in `/etc/pam.d/<service>`.
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies