Setting Up fail2ban Against Brute-Force Attacks
AI generated
$
/etc
Linux · Security · fail2ban · SSH Hardening
Setting Up fail2ban Against Brute-Force Attacks
Reliably protecting SSH and web logins

fail2ban watches log files from SSH and web applications in real time, detects repeated failed logins using configurable patterns, and automatically bans the offending IP address at the firewall. This article shows how administrators can correctly configure jails for SSH and web logins, tune ban times sensibly, and monitor fail2ban activity on an ongoing basis without accidentally locking out legitimate users.

16 min read Jails · Filters · Bantime · Findtime · Maxretry Debian · Ubuntu · systemd · iptables/nftables

1. Why Brute-Force Protection Is Mandatory on Every Linux Server

Every server with an open SSH port or a public login form gets discovered by automated scanners within minutes of going live. These bots try combinations of common usernames like root, admin, or www-data against passwords from leaked lists around the clock. Without a countermeasure, every single attempt gets logged, but nothing stops the attacker from launching the next one immediately. With thousands of attempts per day, the probability of a successful hit rises measurably, especially with weak or reused passwords.

fail2ban closes exactly this gap by turning reactive logging into active defense. Instead of merely documenting that an attack is happening, fail2ban evaluates log files in real time and automatically responds with a firewall block. For Magento operators this is especially relevant, because alongside SSH, the admin backend and often an additional storefront login form are also targeted. A single tool that monitors both attack surfaces simultaneously significantly reduces configuration effort compared to separate solutions per service.

2. How fail2ban Works: Log-Pattern Matching and Firewall Bans

The basic principle of fail2ban consists of three building blocks: a filter, which uses a regular expression to detect failed login attempts in a log file; a jail, which combines filter, log path, and threshold values into an active monitoring unit; and an action, which sets a firewall rule once the threshold is exceeded. fail2ban itself never touches passwords and does not intervene in the login process, it only observes the log output of the monitored service and reacts to patterns within it.

Technically, the fail2ban-server daemon runs permanently in the background and reads new lines either directly from log files or via the systemd journal interface. If the filter detects a configured number of failures (maxretry) from the same IP address within a time window (findtime), the action fires. By default, fail2ban inserts a rule into iptables, nftables, or firewalld that drops all traffic from that IP for the configured bantime. Once the ban period expires, fail2ban automatically removes the rule again.

3. Installation and Basic Configuration: jail.local, filter.d, action.d

Installation on Debian and Ubuntu systems happens through the official package, which already ships with systemd integration and the most common filters for SSH. It is important to understand the difference between jail.conf and jail.local: the jail.conf file gets overwritten on package updates and must therefore never be edited directly. Custom settings always belong in jail.local, which fail2ban automatically loads as an override and which remains untouched during updates. The same rule applies to filter and action overrides in filter.d/*.local and action.d/*.local respectively.

After installation, it is worth looking at /etc/fail2ban/jail.conf to see the available predefined jails, even though nothing gets changed there. The actual configuration then happens centrally in jail.local, with a [DEFAULT] block for global values and individual sections per service. After every change, the service must be reloaded so fail2ban activates the new jails and re-reads existing ban lists.


#!/usr/bin/env bash
# Install fail2ban on Debian/Ubuntu and enable the systemd service
sudo apt update
sudo apt install -y fail2ban

# No need to copy anything manually, jail.local overrides jail.conf automatically
sudo touch /etc/fail2ban/jail.local

# Enable and start the service
sudo systemctl enable --now fail2ban

# Reload after config changes without dropping active bans
sudo fail2ban-client reload

# Verify which jails are currently active
sudo fail2ban-client status

4. Configuring and Hardening the SSH Jail

The sshd jail is already predefined on most distributions but must be explicitly activated with enabled = true. The sshd filter recognizes failed password logins, invalid usernames, and aborted connections based on the messages sshd writes to auth.log or the systemd journal. It is important to set the correct backend value: on systems without a classic /var/log/auth.log file, such as many current Ubuntu and Debian installations, fail2ban reads directly from the journal via backend = systemd instead of waiting on a file that does not exist.

For SSH, a stricter threshold than for other services is recommended, since successful attacks here carry the highest impact. A combination of maxretry = 4, a moderate findtime, and a longer bantime drastically reduces the success rate of dictionary attacks without immediately striking on a single typo. Anyone who additionally moves SSH to a non-standard port and enforces public-key authentication reduces the attack surface even further, fail2ban then remains the second line of defense in case password auth is still enabled for compatibility reasons.


; /etc/fail2ban/jail.local
[DEFAULT]
# Global defaults, overridden per jail below
bantime  = 1h
findtime = 10m
maxretry = 5
backend  = systemd
ignoreip = 127.0.0.1/8 ::1 203.0.113.10

[sshd]
enabled  = true
port     = ssh
filter   = sshd
logpath  = %(sshd_log)s
backend  = systemd
maxretry = 4
bantime  = 2h

[magento-admin]
enabled  = true
port     = http,https
filter   = magento-admin
logpath  = /var/www/html/var/log/system.log
maxretry = 5
findtime = 15m
bantime  = 1h

5. Protecting Web Login Endpoints: Nginx, Magento Admin, WordPress

Web login forms usually require their own filters, because failed login attempts do not appear in the standard access log but in an application log. For areas protected by basic auth, fail2ban ships a ready-made nginx-http-auth filter that recognizes messages from the Nginx error log. For the Magento admin backend there is no official filter, because Magento logs failed logins to var/log/system.log instead of the web server log. A custom filter with a matching failregex is required here and can be built from the actual log format of your Magento version.

The filter must reliably recognize the IP address as the <HOST> placeholder, otherwise the jail never triggers. If the store runs behind a reverse proxy or load balancer, it must also be ensured that the application correctly writes the real client IP via X-Forwarded-For into its log, otherwise fail2ban might, in the worst case, ban the proxy's own IP and take down all traffic. The same principle applies to WordPress installations: a plugin like WP fail2ban writes structured log lines that a simple standard filter can reliably evaluate, without writing any custom regex at all.


; /etc/fail2ban/filter.d/magento-admin.conf
[Definition]
# Matches failed Magento admin login attempts written to system.log
failregex = ^.*Login attempt failed for user.*from ip <HOST>.*$
            ^.*main\.CRITICAL: Invalid login attempt from <HOST>.*$
ignoreregex =

6. Tuning Bantime, Findtime, and Maxretry Correctly

The three core parameters of every jail are directly interrelated and determine how aggressively fail2ban reacts. findtime defines the time window in which failures are counted, maxretry the number of failures within that window before a ban is triggered, and bantime the duration of the block. Values that are too low, such as maxretry = 20 or bantime = 60, give away nearly all of the protective effect, because an attacker either gets enough attempts or simply resumes after a minute.

Values that are too aggressive, on the other hand, carry the risk of locking out real users mid-task over a forgotten password or a VPN switch. A proven practice is progressive ban time via bantime.increment = true: the first ban is short, and each subsequent block against the same IP within the bantime.rndtime history grows exponentially. That way a single typo stays without consequence, while persistent attackers end up locked out for days or weeks after a few cycles. For production SSH access, maxretry = 3-4 with findtime = 10m and bantime = 1h has proven to be a solid starting point, whereas web logins with more frequent legitimate typos warrant a somewhat more generous value.

7. Whitelisting and Preventing Accidental Lockouts

The ignoreip directive in the [DEFAULT] block is the most important safeguard against accidentally locking yourself out. It should contain at minimum 127.0.0.1/8 and ::1, so local health checks and monitoring tools are never banned, as well as fixed IP addresses of admin workstations, office locations, and CI/CD runners that regularly access the server. Without this entry, a failed deploy job from the pipeline can accidentally lock out your own infrastructure, which is especially hard to diagnose in automated environments.

Anyone working from changing IP addresses, for example over mobile connections, should use a VPN with a fixed egress IP instead of a single static address, and whitelist only that one. A second safety mechanism is a documented emergency access path: on a cloud server, a ban can be lifted through the hosting provider's console even without SSH access, using fail2ban-client set sshd unbanip <IP>. Knowing this path in advance saves considerable stress if a lockout actually happens.

8. Monitoring and Log Analysis in Daily Operations

fail2ban is not a fire-and-forget tool, it requires regular checks to confirm that jails are actually catching attempts and not producing false positives. The command fail2ban-client status <jailname> shows the number of currently banned IPs as well as the total number of failures and bans since the last restart. For deeper analysis, /var/log/fail2ban.log records every single ban and unban event with a timestamp, which combines excellently with journalctl or a central log aggregator.

For proactive notification, the action directive can be extended with a mail or webhook action that automatically sends a message to Slack, a monitoring system, or an email address on every ban. This is especially valuable for detecting unusual attack spikes early, for example when ten times the usual number of bans suddenly occurs within an hour, which points to a coordinated campaign rather than isolated scanners. Before every production filter change, fail2ban-regex should also be run against a real log file to catch false or missing matches before rollout.


# Show status and ban count for a specific jail
sudo fail2ban-client status sshd

# Live-tail the fail2ban log for ban and unban events
sudo tail -f /var/log/fail2ban.log

# List all currently banned IPs across all jails
sudo fail2ban-client banned

# Manually unban an IP that was blocked by mistake
sudo fail2ban-client set sshd unbanip 203.0.113.55

# Dry-run a filter against a real log file before enabling it
sudo fail2ban-regex /var/www/html/var/log/system.log \
  /etc/fail2ban/filter.d/magento-admin.conf

{
  "jail": "sshd",
  "action": "ban",
  "ip": "198.51.100.23",
  "failures": 4,
  "timestamp": "2026-07-12T08:14:32+02:00",
  "hostname": "shop-prod-01.mironsoft.de",
  "bantime_seconds": 7200
}

9. fail2ban Compared: Misconfiguration vs. Best Practice

Most fail2ban installations do not fail because of the installation itself, but because of overly lax or inconsistent configuration of the core parameters. The following overview shows the most common mistakes directly compared to the recommended setting.

Area Misconfiguration Recommended Practice Benefit
maxretry maxretry = 20 maxretry = 3-5 Attacker barely gets any attempts
bantime bantime = 60 (1 min) bantime = 1h + increment Repeat offenders stay blocked longer
ignoreip Directive missing entirely ignoreip = 127.0.0.1/8 ::1 admin IP No accidental self-lockout
logpath Wrong path, jail never triggers Verified with fail2ban-regex Jail actually protects
Notification Only a silent iptables entry Mail or webhook action Attack spikes are noticed immediately

The recurring mistake in nearly all examples in the left column: values get copy-pasted once from a tutorial and never adjusted to the actual environment. A server behind a reverse proxy needs different log paths than a server with direct SSH access, and a store with frequent legitimate typos at checkout needs a different findtime than a pure admin backend. The right column only works if it is validated regularly against the real log structure of your own system.

Mironsoft

Server hardening, brute-force protection, and monitoring for Linux infrastructure

fail2ban configured properly, instead of copy-pasted from a tutorial?

We set up jails for SSH and your web login endpoints, tune ban times to your actual traffic, and build monitoring so attack spikes get noticed before a lockout does.

Security Audit

Review existing SSH and web access points for weaknesses and missing jails

Jail Configuration

Custom filters for Magento admin, Nginx, and other login endpoints

Monitoring Setup

Webhook notifications and central log analysis for ban events

10. Summary

fail2ban against brute-force attacks solves a problem every publicly reachable server has: automated login attempts that continue indefinitely without a countermeasure. The interplay of filter, jail, and action turns passive log files into an active defense that automatically blocks suspicious IP addresses at the firewall. For SSH, the predefined jail with adjusted thresholds is usually enough, while web login endpoints like the Magento admin backend generally require a custom filter with a matching failregex built for the application's actual log format.

Fine-tuning bantime, findtime, and maxretry determines whether the protection is effective without locking out legitimate users. A properly maintained ignoreip list prevents the most common cause of accidental self-lockout, and continuous monitoring via fail2ban-client status along with webhook notifications makes visible when and how heavily the server is actually being attacked. fail2ban does not replace fundamental hardening such as public-key authentication or two-factor login, but it adds an automated, permanently active second line of defense.

fail2ban Against Brute-Force Attacks - Key Takeaways

How It Works

A filter detects failures via regex, a jail combines log path and thresholds, and an action sets the firewall block.

SSH and Web Jails

sshd jail usually predefined. Magento admin and other web logins need a custom filter with matching failregex.

Tuning Without Lockouts

maxretry = 3-5, moderate findtime, bantime.increment for progressive bans. Maintain ignoreip for admin IPs.

Monitoring

fail2ban-client status, /var/log/fail2ban.log, and webhook actions for immediate notification on attack spikes.

11. FAQ: fail2ban Against Brute-Force Attacks

1What is fail2ban and how does it fundamentally work?
A daemon that monitors log files for failures via regex and automatically sets a firewall rule against the offending IP once a threshold is exceeded.
2Which log files does fail2ban monitor by default?
Primarily the SSH log via auth.log or the systemd journal. Web login endpoints like Magento need custom log paths and filters.
3How do I configure the SSH jail securely?
enabled = true, correct log path or backend = systemd, maxretry at 3-4 with moderate findtime. Run fail2ban-client reload after changes.
4How do I protect the Magento backend with fail2ban?
A custom filter in filter.d that matches the error messages in var/log/system.log. Behind a reverse proxy, log the real client IP correctly.
5What values for bantime, findtime, maxretry?
maxretry = 3-4, findtime = 10m, bantime = 1h as a starting point. bantime.increment = true extends bans for repeat offenders automatically.
6How do I prevent locking myself out?
Maintain ignoreip in the DEFAULT block with admin IPs, CI/CD runners, and localhost. Use a VPN with a fixed egress IP for changing addresses.
7How do I check whether an IP is currently banned?
fail2ban-client status shows bans for that jail, fail2ban-client banned lists all banned IPs across all jails.
8How do I manually unban an IP?
fail2ban-client set unbanip lifts the ban immediately, if needed via the hosting provider's rescue console without SSH.
9How do I test a filter before rollout?
fail2ban-regex tests the filter against real log lines and shows matches without activating the jail.
10Does fail2ban replace a firewall or 2FA?
No, it is a reactive addition. Public-key auth, 2FA, and a restrictive firewall remain the primary defense.