Skip to main content
COSMICBYTEZLABS
NewsSecurityHOWTOsToolsTraining
StudyProjectsNewsletterHire MeAbout
Subscribe

Press Enter to search or Esc to close

News
Security
HOWTOs
Tools
Training
Study
Projects
Newsletter
Hire Me
About
RSS Feed
Reading List
Subscribe

Stay in the Loop

Get the latest security alerts, tutorials, and tech insights delivered to your inbox.

Subscribe NowFree forever. No spam.
COSMICBYTEZLABS

Your trusted source for IT intelligence, cybersecurity insights, and hands-on technical guides.

1794+ Articles
149+ Guides

CONTENT

  • Latest News
  • Security Alerts
  • HOWTOs
  • Checklists
  • Projects
  • Exam Prep

RESOURCES

  • Search
  • Browse Tags
  • Newsletter Archive
  • Reading List
  • RSS Feed

COMPANY

  • About Us
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CosmicBytez Labs. All rights reserved.

System Status: Operational
  1. Home
  2. HOWTOs
  3. Fail2ban: Automated Brute Force Protection for Linux Servers
Fail2ban: Automated Brute Force Protection for Linux Servers
HOWTOBeginner

Fail2ban: Automated Brute Force Protection for Linux Servers

Deploy Fail2ban to automatically ban IPs hammering your SSH and web services. Covers installation, jail configuration, custom filters, and monitoring active bans.

Dylan H.

Tutorials

July 6, 2026
8 min read

Prerequisites

  • Linux server (Ubuntu 22.04/24.04 or RHEL/AlmaLinux 9)
  • Root or sudo access
  • SSH access to the server
  • Basic familiarity with systemd and log files

Introduction

Every internet-facing Linux server faces a constant stream of automated login attempts. Bots continuously scan public IP space probing SSH on port 22 (and common alternates), trying credential lists thousands of times per hour. Left unchecked, these attacks congest your logs, waste CPU cycles, and — if you have any weak credentials — eventually succeed.

Fail2ban is the standard answer. It tails your log files, detects repeated authentication failures matching configurable patterns, and instructs your firewall (iptables, nftables, or firewalld) to temporarily ban the offending IP. When the ban expires, the IP is unbanned automatically. No manual intervention required.

This guide walks through a production-ready Fail2ban setup: installation, SSH jail configuration, adding jails for Nginx and Apache, writing a custom filter, and verifying the system is actively defending your server.


Prerequisites

Before starting, ensure you have:

  • A running Linux server reachable from the internet
  • systemd for service management (standard on Ubuntu 22.04+, RHEL 9+)
  • A firewall backend — ufw (Ubuntu) or firewalld (RHEL/AlmaLinux) is fine
  • SSH access with sudo rights

Check your OS and kernel:

uname -r
cat /etc/os-release | grep PRETTY_NAME

Step 1 — Install Fail2ban

Ubuntu / Debian

sudo apt update
sudo apt install -y fail2ban

RHEL / AlmaLinux / Rocky Linux 9

Enable EPEL first, then install:

sudo dnf install -y epel-release
sudo dnf install -y fail2ban fail2ban-systemd

Verify the install

fail2ban-client --version
# Fail2Ban v1.x.x

Start and enable the service:

sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban

Step 2 — Understand the Configuration Layout

Fail2ban splits its config into two layers to make upgrades safe:

PathPurpose
/etc/fail2ban/fail2ban.confGlobal defaults (log level, socket path)
/etc/fail2ban/jail.confDefault jail definitions — do not edit directly
/etc/fail2ban/fail2ban.d/Override directory for global settings
/etc/fail2ban/jail.d/Override directory for jail config — edit here
/etc/fail2ban/filter.d/Regex filter definitions for log parsing
/etc/fail2ban/action.d/Ban/unban action scripts (iptables, nftables, etc.)

The override directories (*.d/) take precedence over the base config files. Always put your customizations in .d/ so package upgrades don't clobber your changes.


Step 3 — Create a Local Jail Configuration

Create your primary override file:

sudo nano /etc/fail2ban/jail.d/local.conf

Paste the following, adjusting ignoreip to include your own trusted IPs:

[DEFAULT]
# Space-separated list of IPs/CIDRs to never ban
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.0.0/16 YOUR.ADMIN.IP.HERE
 
# Ban duration: 1 hour (use -1 for permanent)
bantime  = 3600
 
# Window to count failures
findtime = 600
 
# Number of failures before ban
maxretry = 5
 
# Use systemd journal as log backend (modern default)
backend = systemd
 
# Email alerts (optional — set to your address)
# destemail = alerts@yourdomain.com
# sender    = fail2ban@yourserver.com
# mta       = sendmail
# action    = %(action_mwl)s
 
[sshd]
enabled  = true
port     = ssh
logpath  = %(sshd_log)s
maxretry = 4
bantime  = 7200

Key parameters explained:

  • bantime — how long (seconds) an IP stays banned. 3600 = 1 hour.
  • findtime — the sliding window (seconds) in which failures are counted.
  • maxretry — failures within findtime before the ban triggers.
  • ignoreip — never ban these. Include your admin IP to avoid locking yourself out.

Warning: Always add your management/VPN IP to ignoreip before reloading. Banning yourself over SSH requires console access to recover.


Step 4 — Configure the SSH Jail in Detail

The [sshd] jail shown above uses the bundled sshd filter (/etc/fail2ban/filter.d/sshd.conf), which already handles most OpenSSH failure messages. Let's verify the filter matches your actual log entries.

First, find a failed SSH attempt in your journal:

journalctl -u ssh --since "1 hour ago" | grep "Failed\|Invalid\|Connection closed"

Example output:

Jul 06 14:22:11 srv01 sshd[19842]: Failed password for invalid user admin from 185.220.101.45 port 52001 ssh2
Jul 06 14:22:13 srv01 sshd[19843]: Invalid user ubuntu from 185.220.101.45 port 52003

Test the filter against those lines:

sudo fail2ban-regex \
  "Jul 06 14:22:11 srv01 sshd[19842]: Failed password for invalid user admin from 185.220.101.45 port 52001 ssh2" \
  /etc/fail2ban/filter.d/sshd.conf

Expected output shows a match:

Results
=======

Failregex: 1 total
...
Lines: 1 lines, 0 ignored, 1 matched, 0 missed

If lines show "missed", the filter needs adjusting (rare with modern OpenSSH).


Step 5 — Add Jails for Web Services

Nginx (HTTP Auth / Bad Bots)

sudo nano /etc/fail2ban/jail.d/nginx.conf
[nginx-http-auth]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log
maxretry = 6
 
[nginx-limit-req]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log
maxretry = 10
findtime = 60
bantime  = 300

Apache

sudo nano /etc/fail2ban/jail.d/apache.conf
[apache-auth]
enabled  = true
port     = http,https
logpath  = /var/log/apache2/error.log
maxretry = 6
 
[apache-badbots]
enabled  = true
port     = http,https
logpath  = /var/log/apache2/access.log
bantime  = 86400
maxretry = 2

Step 6 — Write a Custom Filter

Suppose your application logs failed API logins like this in /var/log/myapp/auth.log:

2026-07-06T15:04:22Z [AUTH] Failed login for user "badguy" from 203.0.113.55

Create a filter file:

sudo nano /etc/fail2ban/filter.d/myapp-auth.conf
[Definition]
failregex = \[AUTH\] Failed login for user ".*" from <HOST>
ignoreregex =

The <HOST> token is a built-in Fail2ban macro that matches IPv4 and IPv6 addresses.

Test the filter against a real log line:

sudo fail2ban-regex \
  '2026-07-06T15:04:22Z [AUTH] Failed login for user "badguy" from 203.0.113.55' \
  /etc/fail2ban/filter.d/myapp-auth.conf

Wire up the jail:

sudo nano /etc/fail2ban/jail.d/myapp.conf
[myapp-auth]
enabled  = true
port     = 443
logpath  = /var/log/myapp/auth.log
filter   = myapp-auth
maxretry = 5
bantime  = 3600

Step 7 — Choose a Firewall Action

Fail2ban needs to know how to actually block IPs. The action is set globally in [DEFAULT] or per-jail.

Ubuntu with UFW

sudo nano /etc/fail2ban/jail.d/local.conf

Add under [DEFAULT]:

banaction = ufw

RHEL/AlmaLinux with firewalld

banaction = firewallcmd-rich-rules

Plain iptables (any distro)

banaction = iptables-multiport

Reload after any config change:

sudo fail2ban-client reload

Step 8 — Apply Configuration and Reload

After any edit to jail or filter files, reload without restarting:

sudo fail2ban-client reload

To reload a single jail only:

sudo fail2ban-client reload sshd

Check that all enabled jails are running:

sudo fail2ban-client status

Output should list your active jails:

Status
|- Number of jail:      4
`- Jail list:   myapp-auth, nginx-http-auth, nginx-limit-req, sshd

Verification and Testing

View ban status for a jail

sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 3
|  |- Total failed:     47
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 2
   |- Total banned:     12
   `- Banned IP list:   185.220.101.45 45.155.205.10

Manually ban an IP (for testing)

sudo fail2ban-client set sshd banip 198.51.100.99

Verify with iptables:

sudo iptables -L f2b-sshd -n --line-numbers

Manually unban an IP

sudo fail2ban-client set sshd unbanip 198.51.100.99

Watch bans happen in real time

sudo journalctl -fu fail2ban

Look for lines like:

fail2ban.actions [INFO]  Ban 185.220.101.45
fail2ban.actions [INFO]  Unban 45.155.205.10

Simulate SSH brute force (from a safe test IP)

On a second machine you don't mind getting banned, attempt several failed SSH logins:

for i in {1..6}; do ssh baduser@YOUR_SERVER_IP; done

Within seconds, the IP should appear in fail2ban-client status sshd.


Troubleshooting

Fail2ban not starting:

Check the config for syntax errors:

sudo fail2ban-client --test

Jail not triggering bans:

  1. Confirm the log path exists and is readable: sudo ls -l /var/log/auth.log
  2. Test the filter directly with fail2ban-regex against a real log line.
  3. Check backend: if using systemd, the logpath is ignored — the journal is used.
  4. Increase verbosity temporarily in /etc/fail2ban/fail2ban.conf: set loglevel = DEBUG, reload, then check journalctl -u fail2ban.

You banned yourself:

If you can still reach the server another way (console, second IP), unban yourself:

sudo fail2ban-client set sshd unbanip YOUR.IP.ADDRESS

If locked out entirely, access via your hosting provider's out-of-band console and run the above. Then add your IP to ignoreip immediately.

Bans not persisting across Fail2ban restarts:

By default, bans are in-memory only. To persist them across restarts, add to [DEFAULT]:

dbfile   = /var/lib/fail2ban/fail2ban.sqlite3
dbpurgeage = 86400

Fail2ban will restore active bans from the database on startup.

High log volume causing lag:

If findtime is long and logs are large, Fail2ban can consume CPU scanning history. Reduce findtime or switch from pyinotify to systemd backend for better performance.


Summary

You now have Fail2ban defending your server with:

  • SSH jail with tightened maxretry and extended bantime
  • Nginx/Apache jails blocking web authentication abuse
  • Custom application filter matched to your own log format
  • Safe ignoreip list protecting your admin IPs from self-banning
  • Persistent ban database surviving service restarts

Fail2ban is a lightweight but effective first line of defence against automated attacks. Pair it with SSH key-only authentication (disable password login entirely in sshd_config) and a firewall that restricts SSH to known CIDR ranges where possible. For deeper intrusion detection, consider layering Fail2ban with CrowdSec for community threat intelligence, or Wazuh SIEM/XDR for centralized log correlation across your fleet.

#fail2ban#linux#ssh-hardening#brute-force#intrusion-prevention#server-security#sysadmin

Related Articles

CrowdSec: Deploy a Community-Powered Intrusion Prevention System

Install and configure CrowdSec on Linux to detect and block attacks using crowdsourced threat intelligence, custom scenarios, and iptables/nftables bouncers.

6 min read

Linux auditd: Kernel-Level Security Monitoring and Compliance Logging

Deploy and tune the Linux Audit Framework (auditd) to capture privileged operations, file access, and authentication events — building a tamper-resistant...

6 min read

Deploy OpenCanary to Catch Attackers Inside Your Network

Set up OpenCanary honeypot services on a Raspberry Pi or VM to detect lateral movement, credential stuffing, and unauthorized access before attackers...

9 min read
Back to all HOWTOs