Server

fail2ban in Practice

By Prabath Thalangama· October 24, 2026· 3 min read
#linux#security#fail2ban

Introduction

fail2ban scans log files for patterns that indicate abuse (repeated failed SSH logins, web auth failures, spam attempts), and adds a temporary firewall block for the offending IP. It's a blunt but effective way to cut brute-force noise.

The pieces

  • Jail — a monitored service: which log, which filter, how many failures in what window trigger a ban, for how long, and what ban action.
  • Filter — a set of regexes (failregex) that match a "this was a failed attempt" log line and capture the source IP as <HOST>.
  • Action — what to do on a ban (add a firewall rule, send an email, call a script).

Configure via jail.local

Never edit jail.conf (package-owned). Create /etc/fail2ban/jail.local:

[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
backend  = systemd                 # read the journal, not files (modern default)
banaction = nftables-multiport     # or firewalld, or iptables-multiport
ignoreip  = 127.0.0.1/8 ::1 203.0.113.0/24    # YOUR admin ranges — do this first

[sshd]
enabled = true

[nginx-http-auth]
enabled = true
logpath = /var/log/nginx/error.log

[recidive]
enabled  = true
logpath  = /var/log/fail2ban.log
bantime  = 1w
findtime = 1d
maxretry = 5
  • ignoreip — allowlist your own admin IPs before enabling anything, or you'll ban yourself during testing.
  • backend = systemd — reads journald directly; no logpath needed for services that log there (sshd). File-based jails still need logpath.
  • recidive — bans IPs that keep coming back after their first ban expires, for much longer. Watches fail2ban's own log.
  • Incremental bans: bantime.increment = true, bantime.factor = 2 doubles the ban each repeat offence.

Writing a filter

/etc/fail2ban/filter.d/myapp.conf:

[Definition]
failregex = ^.*authentication failed for user .* from <HOST>.*$
            ^.*invalid API key from <HOST>.*$
ignoreregex =
datepattern = %%Y-%%m-%%dT%%H:%%M:%%S

<HOST> is the magic token that captures the IP (or hostname). %% escapes % in the ini file.

Ban actions

  • nftables-multiport / nftables-allports — adds the IP to an nft set (fail2ban table). Best on nft-based systems.
  • firewalld-multiport / firewalld ipset — integrates with firewalld.
  • iptables-multiport — legacy.
  • Match the action to your firewall so bans actually take effect and survive a firewall reload (nftables/firewalld sets persist better than raw iptables rules).

Verification and troubleshooting

fail2ban-client status                       # enabled jails
fail2ban-client status sshd                   # currently banned, total, log path
fail2ban-client set sshd unbanip 198.51.100.5
fail2ban-client set sshd banip 198.51.100.66  # manual ban (test the action)

# Test a filter against a log without banning
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
fail2ban-regex --print-all-matched systemd-journal[unit=nginx.service] filter.d/nginx-http-auth.conf
  • Jail enabled but never bans — the filter's failregex doesn't match the actual log lines (log format changed, wrong datepattern). fail2ban-regex shows matched/missed counts. Also check logpath exists and fail2ban can read it, or backend = systemd for journald services.
  • Bans happen but the IP still connects — the ban action doesn't match your firewall (added an iptables rule but you use nftables/firewalld), or a higher-priority allow rule. nft list set inet fail2ban ... / firewall-cmd --list-all to see if the ban is really in the firewall.
  • You banned yourself — console access; fail2ban-client unban --all or set <jail> unbanip <ip>, then add your range to ignoreip and restart.
  • fail2ban.service fails to start — a syntax error in jail.local / a filter; fail2ban-client -d dumps the parsed config, journalctl -u fail2ban.
  • Too aggressive / legit users banned — maxretry too low or findtime too long for a service where users fat-finger passwords; raise maxretry, shorten findtime, or narrow the failregex to genuine attacks.
  • IPv6 not banned — old fail2ban or an action without allports/multiport IPv6 support; modern versions + nftables handle both.
  • Cloud / behind a proxy — the log shows the proxy's IP, not the client's. Configure the app to log X-Forwarded-For and adjust the filter, or ban at the proxy layer instead.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.