Every day, automated bots scan thousands of servers, trying password after password until they find a weak spot and break in. One successful breach can compromise your entire server.
Fail2Ban acts as an automated security guard, monitoring your server logs and blocking suspicious IP addresses before they cause damage. It monitors authentication attempts and automatically bans attackers.
Understanding Fail2Ban
Fail2Ban is an open source intrusion prevention tool written in Python. It monitors your server log files and looks for patterns that indicate malicious activity. When someone tries logging into your SSH service with the wrong password multiple times, Fail2Ban notices this behavior. After a set number of failed attempts, it automatically adds their IP address to your firewall and blocks them for a specific duration. You can protect any service that writes authentication attempts to log files. This includes SSH, Apache, Nginx, and dozens of other services.
How Fail2Ban works
Fail2Ban reads log files looking for failed authentication attempts. It uses regex patterns to identify suspicious entries. When it finds a match, it counts how many times that IP address failed within a specific time window. Once the count exceeds your threshold, Fail2Ban takes action.
It communicates with your firewall to block that IP address. Modern systems have used nftables as the default firewall framework since 2019. Older systems use iptables. Fail2Ban supports both but requires explicit configuration. Bans typically last from minutes to days, depending on your configuration. After the ban period expires, Fail2Ban automatically removes the IP address.
Key components
Jails define which service to protect, which log file to monitor, and what action to take when violations occur. Filters define the patterns Fail2Ban looks for in log files. These regex patterns match suspicious log entries. Actions determine what happens when an IP gets flagged. The most common action is adding the IP to your firewall block list.
Getting started
Prerequisites
Before installing Fail2Ban, ensure you have these requirements in place. This guide requires root or sudo access on Debian 10+, Ubuntu 20.04+, or similar distributions. Your system should have a firewall configured (nftables, iptables, or UFW) and basic command line familiarity.
Fail2Ban version 1.1.0 and higher requires Python 3.6 or higher. Version 1.1.0 completely removed Python 2 support in April 2024. Check your Python version with python3 --version to confirm compatibility.
Know your current public IP address before starting. Run curl ifconfig.me from your local machine to find it. This prevents locking yourself out during configuration.
Installing and verifying Fail2Ban
Update your package lists and install Fail2Ban with the Python systemd library for modern log integration.
systemctl enable --now fail2ban
You should see an active running status.
fail2ban.service - Fail2Ban Service
Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled)
Active: active (running) since Wed 2025-12-11 10:30:22 UTC
Check your Fail2Ban version and verify systemd backend support.
fail2ban-client --version
fail2ban-client --dp
The second command should show systemd in the available backends list.
Check system configuration
Identify which firewall framework your system uses before configuring Fail2Ban.
Check if your system uses nftables, the standard since Debian 10 and Ubuntu 20.04.
nft list ruleset
If this shows rules, you use nftables. If it returns an error, check for iptables.
iptables -L
If this shows rules, you use iptables.
Verify that your system uses systemd journal for logging.
journalctl -u ssh -n 5
If this shows recent SSH log entries, your system uses systemd journal and you must configure Fail2Ban to use the systemd backend.
Configuring SSH protection
SSH protection should be your first priority. Successful SSH access gives attackers complete control.
Never edit jail.conf directly because updates will overwrite your changes. Create a jail.local file instead.
nano /etc/fail2ban/jail.local
Add this complete configuration. Replace YOUR_ADMIN_IP_HERE with your actual IP address from the earlier curl command.
[DEFAULT]
## === SECURITY: PREVENT SELF-LOCKOUT ===
ignoreip = 127.0.0.1/8 ::1 YOUR_ADMIN_IP_HERE
## === BAN SETTINGS ===
bantime = 3600
findtime = 600
maxretry = 3
## === FIREWALL INTEGRATION ===
banaction = nftables-multiport
banaction_allports = nftables[type=allports]
## === LOG INTEGRATION ===
backend = systemd
## === DATABASE MAINTENANCE ===
dbpurgeage = 86400
[sshd]
enabled = true
port = ssh
filter = sshd
backend = systemd
maxretry = 3
bantime = 3600
findtime = 600
The ignoreip setting prevents Fail2Ban from ever banning specified addresses, protecting you from self-lockout during testing and administration.
The banaction settings tell Fail2Ban which firewall to use. Since 2019, nftables has been the default on modern distributions. If your system uses iptables, change these to iptables-multiport and iptables-allports. If you use UFW, change both to ufw.
The backend = systemd setting is critical for modern systems. Services like SSH now log to the systemd journal rather than traditional log files. This setting tells Fail2Ban to read directly from the systemd journal, providing better performance.
The dbpurgeage setting removes bans older than 24 hours from the database, keeping it manageable. For high traffic servers, you can reduce this to 43200 (12 hours) for more aggressive cleanup.
If you changed your SSH port from the default port 22, update the port setting. For example, if you moved SSH to port 2222, use port = 2222. Check your SSH port with grep Port /etc/ssh/sshd_config.
Test your configuration syntax before applying it.
fail2ban-client -t
If the test passes, restart Fail2Ban.
systemctl restart fail2ban
Verify that your SSH jail loaded correctly.
fail2ban-client status
You should see output showing your active jails.
Status
\|- Number of jail: 1
`- Jail list: sshd
Testing your configuration
Follow these steps to verify that Fail2Ban works correctly.
Step 1: verify Fail2Ban is running
Check that Fail2Ban recognizes your jails.
systemctl status fail2ban
fail2ban-client status
Your SSH jail should appear in the jail list.
Step 2: perform a test ban
From a different machine where you are not logged in, attempt SSH login with wrong passwords three times. Verify that you added your regular admin IP to ignoreip before testing.
Step 3: verify the ban took effect
Check if the ban worked.
fail2ban-client status sshd
You should see the test IP in the banned list.
Status for the jail: sshd
\|- Filter
\| |- Currently failed: 0
\| |- Total failed: 3
\| `- File list: systemd-journal
`- Actions
\|- Currently banned: 1
\|- Total banned: 1
`- Banned IP list: 203.0.113.45
Check the log for ban confirmation.
grep 'Ban' /var/log/fail2ban.log | tail -5
Step 4: remove the test ban
Unban your test IP.
fail2ban-client set sshd unbanip 203.0.113.45
Advanced protection
Protecting repeat offenders
Sophisticated attackers may wait out bans and try again. The recidive jail catches these repeat offenders.
Add this to your jail.local file.
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = nftables-allports
bantime = 604800
findtime = 86400
maxretry = 3
protocol = all
This bans repeat offenders for one week. If an IP gets banned three times within 24 hours across any jails, recidive triggers the longer ban. Change banaction to iptables-allports for iptables systems or ufw for UFW systems.
Protecting web applications
Web services need protection too. Fail2Ban can secure any service that logs authentication attempts.
Securing Nextcloud
Create a custom filter for Nextcloud.
nano /etc/fail2ban/filter.d/nextcloud.conf
Add this filter definition.
[Definition]
failregex = Login failed.*REMOTE_ADDR=
^.*Trusted domain error.*from
ignoreregex =
Add the jail to your jail.local file. The logpath depends on your installation method.
[nextcloud]
enabled = true
port = http,https
filter = nextcloud
maxretry = 3
bantime = 3600
logpath = /var/www/nextcloud/data/nextcloud.log
For Snap installations, use
logpath = /var/snap/nextcloud/current/logs/nextcloud.log.
For Docker installations, Fail2Ban should always run on the host system, not inside containers, as it requires direct firewall and log access. First, find your volume name with docker volume ls, then inspect it to get the correct path.
docker volume ls
docker volume inspect YOUR_VOLUME_NAME | grep Mountpoint
Use the discovered path in your configuration. For example, if your volume is named nextcloud_data, the path might be
/var/lib/docker/volumes/nextcloud_data/_data/nextcloud.log
Developing custom filters
When creating filters for applications not included with Fail2Ban, follow this process. First, identify the log location and format. Find failed authentication entries in the logs.
Create a regex pattern matching those entries. Test with fail2ban-regex to verify it works.
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
Successful output looks like this:
Running tests
=============
Use failregex filter file : sshd
Use log file : /var/log/auth.log
Results
=======
Failregex: 3 total
Lines: 3 lines, 0 ignored, 3 matched, 0 missed
This confirms your filter correctly matches failed authentication attempts.
After adding new jails, restart Fail2Ban and verify they have loaded.
systemctl restart fail2ban
fail2ban-client status
Configure external alerting
Keep the firewall ban action and add an HTTPS notification action instead of installing an SMTP server or relay on the VPS. The example below follows Fail2Ban's action-file model and ntfy's HTTP publishing interface. Use a dedicated ntfy account, a private topic, and an expiring access token; protect the token as a secret.
Create a custom action file:
nano /etc/fail2ban/action.d/ntfy.conf
Add this definition, then replace the placeholder topic and token with values from your external ntfy service. The request sends only the jail name, banned IP, and failure count.
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = curl --fail --silent --show-error --max-time 10 -H "Authorization: Bearer <token>" -H "Title: Fail2Ban <name>" -H "Priority: high" -H "Tags: warning,shield" --data "Banned <ip> in jail <name> after <failures> failures" "<server>/<topic>"
actionunban =
[Init]
server = https://ntfy.sh
topic = REPLACE_WITH_PRIVATE_TOPIC
token = tk_REPLACE_WITH_ACCESS_TOKEN
Fail2Ban substitutes its name, ip, and failures action tags when a ban occurs. The token, server, and topic values come from the action file's [Init] section. The ntfy project documents token creation and expiry in its access-token guide.
In the [DEFAULT] section of jail.local, keep the normal firewall action and append the external notification action:
[DEFAULT]
action = %(action_)s
ntfy
Restrict the configuration files, validate the complete Fail2Ban configuration, and restart only after validation passes:
chmod 600 /etc/fail2ban/action.d/ntfy.conf /etc/fail2ban/jail.local
fail2ban-client -t
systemctl restart fail2ban
Confirm that the jail still bans through the configured firewall and that the external ntfy client receives the alert. If delivery fails, inspect the Fail2Ban log and test the ntfy HTTPS endpoint separately; do not install a local mail server as a fallback. Virtarix VPS/VDS do not support mail-server workloads and email ports are blocked.