This guide shows how to install Fail2Ban on Debian Linux and configure it to protect your server from brute-force attacks and automated intrusion attempts. Fail2Ban monitors log files for repeated failed login attempts or suspicious behavior and automatically bans offending IP addresses by updating firewall rules. By following this guide, you will have SSH protection enabled immediately, customized jail settings for web servers and mail services, and the tools to manage bans, monitor logs, and troubleshoot configuration issues.
Debian 13 (Trixie) is the current stable release and uses nftables as the default firewall backend. Debian 12 (Bookworm) remains supported as oldstable and defaults to iptables-multiport. Debian 11 (Bullseye) remains in LTS support until August 2026 with iptables-multiport as its default. All three versions work with the configurations in this guide.
What Is Fail2Ban?
Fail2Ban is an intrusion prevention framework that protects Linux servers from automated attacks. Think of it as a security guard that watches your server’s log files around the clock, looking for signs of malicious activity like repeated failed login attempts, password guessing, or exploit scanning. When Fail2Ban detects suspicious patterns, it automatically blocks the attacker’s IP address by adding firewall rules, preventing further access for a configurable period.
The tool works with jails, which are monitoring rules for specific services. Each jail watches a particular log file (such as /var/log/auth.log for SSH) and applies filters to detect attack patterns. When an IP address triggers too many failures within a defined time window, Fail2Ban executes a ban action that blocks the offender at the firewall level. This approach stops brute-force attacks before they succeed while allowing legitimate users to continue accessing your server.
Fail2Ban supports multiple firewall backends including iptables, nftables, and UFW. Debian 13 defaults to nftables, while Debian 11 and 12 default to iptables-multiport. You can configure any backend to match your environment. Fail2Ban includes pre-configured jails for common services like SSH, Apache, Nginx, Postfix, and Dovecot, making initial setup straightforward for most server environments.
Update Debian Before Installation
Before installing Fail2Ban, update your Debian operating system to ensure all existing packages are current. This step refreshes your package index and applies security patches so Fail2Ban installs on a stable foundation. Run the following command in your terminal:
sudo apt update && sudo apt upgrade -y
Install Fail2Ban from Debian Default Repositories
Fail2Ban lives in Debian’s default repositories, so no extra sources or third-party repositories are required. Install it with a single command:
| Debian Version | Fail2Ban Version | Python Version | Default Backend |
|---|---|---|---|
| Debian 13 (Trixie) | 1.1.0 | Python 3.13 | nftables |
| Debian 12 (Bookworm) | 1.0.2 | Python 3.11 | iptables-multiport |
| Debian 11 (Bullseye) | 0.11.2 | Python 3.9 | iptables-multiport |
sudo apt install fail2ban
This command pulls in Fail2Ban and its firewall backends so you can start protecting services immediately.
Confirm Fail2Ban Installation
Verify that Fail2Ban installed correctly by checking its version:
fail2ban-client --version
The output displays the installed version:
Fail2Ban v1.1.0
Debian 12 (Bookworm) shows version 1.0.2, and Debian 11 (Bullseye) shows version 0.11.2. Any of these versions confirms a successful installation and works with the configurations in this guide.
Confirm the package source:
apt-cache policy fail2ban
The output shows the installed version and the repository source:
fail2ban:
Installed: 1.1.0-8
Candidate: 1.1.0-8
Version table:
*** 1.1.0-8 500
500 http://deb.debian.org/debian trixie/main amd64 Packages
100 /var/lib/dpkg/status
Verify Fail2Ban Service Status
Fail2Ban starts automatically and is enabled to run on boot. Verify the service is running:
systemctl status fail2ban
When running correctly, the output shows an active status:
● fail2ban.service - Fail2Ban Service
Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-11-25 10:15:32 UTC; 5min ago
Docs: man:fail2ban(1)
Main PID: 1234 (fail2ban-server)
Tasks: 5 (limit: 4915)
Memory: 14.2M
CPU: 234ms
CGroup: /system.slice/fail2ban.service
└─1234 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
If the service shows as inactive, start it and enable it on boot in a single step:
sudo systemctl enable --now fail2ban
This command starts Fail2Ban immediately and keeps it running after every reboot.
Install UFW (Optional)
If you prefer using Uncomplicated Firewall (UFW) with Fail2Ban instead of iptables, follow the steps below. UFW provides a user-friendly frontend to iptables and simplifies firewall rule management. Note that Debian omits UFW by default, so you must install it manually if you want this alternative firewall backend.
Install UFW
Install UFW on your Debian system with the following command:
sudo apt install ufw
Verify UFW Installation
Verify the installation by checking the version:
ufw version
The output displays the installed version:
ufw 0.36.2 Copyright 2008-2023 Canonical Ltd.
Enable UFW
Enable UFW on your system to activate the firewall and ensure it starts automatically when your Debian server boots. If you are connected over SSH, allow the service first so you do not lock yourself out:
sudo ufw allow OpenSSH
After allowing SSH (or other required services), turn on the firewall:
sudo ufw enable
This starts UFW and loads it automatically at boot. The output should show:
Firewall is active and enabled on system startup
This output confirms that UFW is active and will start automatically when your Debian server is rebooted.
Back Up Fail2Ban Configuration Files
Fail2Ban comes with two default configuration files located at /etc/fail2ban/jail.conf and /etc/fail2ban/jail.d/defaults-debian.conf. Before making any changes, create a backup copy to preserve your customizations across package updates.
Do not modify the default .conf files directly. Package updates overwrite them.
Create copies of the configuration files with the .local extension to preserve your custom settings. Fail2Ban reads .local files in preference to .conf files, so your customizations remain intact during package upgrades and you always have the original to reference.
Create jail.local
Copy the default configuration file to create your local version:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Fail2Ban prioritizes jail.local over the default, so your customizations persist through package updates.
Customize Fail2Ban Configuration for Your Server
Adjust the settings in jail.local to meet your server’s security requirements. Jails are pre-configured protection rules that monitor specific services (SSH, Apache, Postfix) for attack patterns and automatically ban offending IPs when thresholds are exceeded. The configuration process involves selecting your firewall backend, setting ban times, whitelisting trusted IPs, and enabling jails for the services you run.
Choose Your Firewall Backend
Fail2Ban supports multiple firewall backends for banning IP addresses. Debian 13 defaults to nftables through /etc/fail2ban/jail.d/defaults-debian.conf, while Debian 11 and 12 default to iptables-multiport. You can override any backend by editing your jail.local file. Understanding the differences helps you choose the right backend:
| Backend | Status | Best For | Trade-offs |
|---|---|---|---|
| nftables | Default on Debian 13 | Modern systems, better performance with large rule sets, future-proof | Different syntax from iptables if you need manual rules |
| iptables | Default on Debian 11/12 | General use, mature tooling, widespread documentation | Older technology, nftables is the replacement |
| UFW | Manual install required | Desktop systems, simple firewall setups, user-friendly rule management | Extra package dependency, adds abstraction layer |
Override with iptables (Debian 13 Only)
Debian 11 and 12 already use iptables-multiport by default, so no changes are needed. Debian 13 users who prefer iptables for consistency with older systems can add these settings to the [DEFAULT] section in jail.local:
[DEFAULT] # Override nftables default with iptables (Debian 13 only) banaction = iptables-multiport banaction_allports = iptables-allports
Check which firewall subsystem your system uses by running nft list tables (nftables) or iptables -L (iptables). For firewalld users, Fail2Ban includes firewalld actions as well.
Configure Log Backend for Systemd Journal
On Debian 12 and Debian 13, systems using systemd-journald for logging benefit from setting the backend to systemd. This configuration tells Fail2Ban to read logs directly from the systemd journal instead of polling traditional log files, which improves reliability and prevents startup issues on systems where log file paths differ or journald is the primary logging mechanism.
Add this setting to the [DEFAULT] section in jail.local to enable the systemd backend:
Systemd Journal Backend Configuration
[DEFAULT] # Use systemd journal backend (recommended for Debian 12+) backend = systemd
This backend setting is independent from the firewall banaction configuration shown above. The backend parameter controls how Fail2Ban reads logs, while banaction controls how it blocks IPs. Systems running Debian 12 or newer should use backend = systemd for better integration with journald, especially if you encounter startup failures or Fail2Ban reports that it cannot find log files.
Use the following examples as starting points. Every environment differs, so monitor logs and test changes to avoid false positives that could block legitimate users.
Edit the Configuration File
Open the configuration file for editing:
sudo nano /etc/fail2ban/jail.local
Enable Incremental Ban Times for Repeat Offenders
The ban time increment feature increases ban duration for repeat offenders. This progressive penalty discourages persistent attackers by doubling the ban duration each time they trigger another offense:
Ban Time Multiplier Configuration
## Ban time multipliers bantime.increment = true bantime.factor = 2 bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor
These settings double the ban time for each subsequent offense, discouraging persistent attackers.
Whitelist IPs in Fail2Ban
To whitelist specific IP addresses, uncomment the ignoreip line and add the desired IP addresses, separated by spaces or commas. IP ranges can also be whitelisted.
IP Whitelist Configuration
ignoreip = 127.0.0.1/8 ::1 203.0.113.25 198.51.100.10
This example whitelists localhost, IPv6 loopback, and two trusted public addresses. Whitelist admin workstations or monitoring systems to prevent accidental bans.
Configure Default Ban Time and Thresholds
By default, Fail2Ban bans an attacker for 10 minutes after five failed attempts within a 10-minute window. These conservative defaults balance security with usability, preventing accidental lockouts while blocking automated attacks. You can adjust these default settings, but it is recommended to set custom ban times and retry limits for different jails based on service sensitivity:
Default Ban Thresholds
[DEFAULT] # "bantime" is the number of seconds that a host is banned. bantime = 10m # A host is banned if it has generated "maxretry" during the last "findtime" seconds. findtime = 10m # "maxretry" is the number of failures before a host get banned. maxretry = 5
In this example, the default settings ban an attacker for 10 minutes after five failed attempts in a 10-minute window. Adjust bantime, findtime, and maxretry to match your security posture. Consider increasing ban times for SSH (1-24 hours) while keeping shorter bans for web services where false positives are more likely.
Enable SSH Protection Jail
The SSH jail protects your server from brute-force login attempts targeting SSH, the most commonly attacked service on internet-facing servers. Debian’s default Fail2Ban configuration already enables the sshd jail through /etc/fail2ban/jail.d/defaults-debian.conf, so SSH protection is active immediately after installation with no changes required.
To customize the SSH jail behavior (ban time, retry limits), add or modify the [sshd] section in jail.local:
Custom SSH Jail Configuration
[sshd] maxretry = 3 bantime = 1h findtime = 10m
This configuration bans IP addresses for 1 hour after 3 failed login attempts within a 10-minute window. The default jail already sets enabled = true, so you only need to specify the settings you want to change. Servers exposed to the public internet benefit from tighter limits (maxretry = 3) and longer bans (1-24 hours).
Debian 13 automatically configures the sshd jail to use the systemd backend (reading directly from journald) and nftables for banning. Debian 11 and 12 use file-based log polling (
/var/log/auth.log) and iptables. If your Debian 11 or 12 system uses journald without rsyslog (common in minimal installations), addbackend = systemdto the [DEFAULT] section in jail.local. See the “Configure Log Backend for Systemd Journal” section above for details.
Configure UFW as Fail2Ban Backend
To configure Fail2Ban to use UFW as the default banning action instead of iptables, update the banaction line in the [DEFAULT] section.
UFW Backend Configuration
[DEFAULT] banaction = ufw
This configures Fail2Ban to use UFW for ban management instead of iptables. Use this if UFW is your primary firewall and you want consistent rule management.
Configure Email Alerts and Notifications
Fail2Ban can send email notifications with whois reports when attacks occur. Email alerts help you identify patterns and respond to security incidents. Set the destination email and sender address in the [DEFAULT] section. An outbound mail service (Postfix, Exim, or an SMTP relay) must be running for alerts to leave the server.
Email Alert Configuration
destemail = root@yourdomain.com sender = fail2ban@yourdomain.com
In this example, root@yourdomain.com receives notifications, and fail2ban@yourdomain.com sends them. Email alerts keep you informed about security incidents so you can respond quickly.
Enable Additional Service Protection Jails
Beyond SSH protection, Fail2Ban includes jails for web servers, mail services, FTP daemons, and other network applications. Enable jails only for services you actually run on your server to avoid unnecessary log monitoring overhead. Each additional jail consumes system resources and increases log processing time, so configure only what you need.
To enable a jail, add “enabled = true” in the corresponding jail section. Below are common jails and their purposes:
Example: Protect Apache from Bad Bots
If you run Apache on your server, enable the apache-badbots jail to block malicious crawlers and bots:
[apache-badbots] enabled = true port = http,https logpath = %(apache_access_log)s bantime = 48h maxretry = 1
In this example, the apache-badbots jail bans offending IPs for 48 hours after a single detection. Enable only the jails that match the services on your server to balance coverage and performance.
You can also add custom actions or use actions from the action.d directory by updating the action line in the jail section.
Apache Botsearch Jail with Cloudflare Integration
[apache-botsearch]
enabled = true
port = http,https
logpath = %(apache_error_log)s
action = %(action_mw)s
cloudflare[auth_token=example_token, zone=example.com]
bantime = 72h
maxretry = 1
This example emails a warning and blocks the IP at Cloudflare’s edge. Replace example_token with a Cloudflare API token that has Zone.Firewall.Edit permissions, and set zone to your domain. Create API tokens in the Cloudflare dashboard under My Profile > API Tokens.
Restart Fail2Ban Service
Restart the service to apply configuration changes:
sudo systemctl restart fail2ban
Ban and Unban IP Addresses Manually
Manage IP bans using the fail2ban-client command. Manual ban management lets you block known threats proactively or unban legitimate users who triggered false positives.
Ban an IP Address Manually
To ban an IP address manually for a specific jail (e.g., apache-botsearch), use the following command:
sudo fail2ban-client set apache-botsearch banip <ip_address>
This adds the specified IP to the apache-botsearch jail’s ban list. Manual bans persist until you remove them or restart the Fail2Ban service.
Unban an IP Address
To unban an IP address for a specific jail (e.g., apache-botsearch), use the following command:
sudo fail2ban-client set apache-botsearch unbanip <ip_address>
This command removes the ban for the specified IP address, allowing it to access your server again, assuming it complies with the rules in the apache-botsearch jail. Use this when you have confirmed the IP belongs to a legitimate user or service that was mistakenly banned.
Access the Help Menu
To access the help menu and view additional settings or commands, use the following command:
sudo fail2ban-client -h
Additional Commands
List enabled jails and summary:
sudo fail2ban-client status
The output lists all active jails:
Status |- Number of jail: 2 `- Jail list: apache-badbots, sshd
Check the status of a specific jail:
sudo fail2ban-client status sshd
The output shows current ban statistics for the jail:
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: 198.51.100.45 203.0.113.78
Reload the configuration without restarting the Fail2Ban service:
sudo fail2ban-client reload
This command reloads Fail2Ban’s configuration, applying any changes you made without restarting the service.
Check the list of currently banned IP addresses for a specific jail:
sudo fail2ban-client get apache-botsearch banned
This command lists all the IP addresses currently banned by the apache-botsearch jail.
These commands provide the tools to manage IP bans using Fail2Ban effectively. Replace <ip_address> with the actual IP you want to ban or unban, and swap apache-botsearch for the jail you want to manage.
Custom ban durations are configured in jail.local using the bantime parameter, not through the fail2ban-client command. To set different ban times for specific jails, add
bantime = 1h(or your preferred duration) to that jail’s configuration section.
Check and Monitor Fail2Ban Logs
Review Fail2Ban logs to verify your jails are working correctly. Fail2Ban logs activity to /var/log/fail2ban.log by default.
Monitor Fail2Ban Logs in Real Time
To watch the logs live and spot any issues while working on your server, use tail -f:
tail -f /var/log/fail2ban.log
This command streams new log entries as they appear, helping you spot unusual activity or errors quickly.
Search Logs for Specific Information
Use the grep command to search for specific information within the logs, such as IP addresses, user agents, or errors. Grep filters the log file and displays only the lines that contain the specified keyword.
Example user-agent:
grep "Bing" /var/log/fail2ban.log
This command searches for all log entries containing the word “Bing,” which might help you identify unwanted bot activity or user agents.
Example error:
grep "error" /var/log/fail2ban.log
This command searches for log entries containing the word “error,” helping you spot any issues or problems with Fail2Ban’s operation.
Example IP address:
grep "198.51.100.1" /var/log/fail2ban.log
This command searches for log entries containing the specified IP address, allowing you to track a specific IP’s activity or determine if it has been banned.
These examples demonstrate various ways to filter and search your logs using terminal commands. Regularly reviewing your Fail2Ban logs will help you maintain your server’s security and ensure your jails are working as intended.
Troubleshoot Fail2Ban Issues
If Fail2Ban is not working as expected, use these diagnostic commands to identify and resolve common issues.
Check Configuration Syntax
Before restarting Fail2Ban after making configuration changes, verify the syntax is correct:
sudo fail2ban-client -t
When the configuration is valid, you see:
OK: configuration test is successful
If errors exist, Fail2Ban displays the problematic file and line number:
ERROR: Failed during configuration: Bad jail name 'sshd'. Use 'filter = sshd' Error in FilterReader (reading filter configuration of jail 'sshd')
Fix the indicated syntax error and run the test again before restarting the service.
Test Filter Regex Patterns
If a jail is not detecting attacks, test the filter regex against the actual log file:
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
The output shows how many lines match the filter patterns:
Running tests ============= Use failregex filter file : sshd, basedir: /etc/fail2ban Use log file : /var/log/auth.log Use encoding : UTF-8 Results ======= Failregex: 47 total |- #) [# of hits] regular expression | 1) [47] ^\S+ sshd\[\d+\]: Failed .+ for .* from( port \d+)?( ssh\d?)?$ `- Ignoreregex: 0 total Date template hits: |- [# of hits] date format | [1823] {^LN-BEG}(?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)? `- Lines: 1823 lines, 0 ignored, 47 matched, 1776 missed
A healthy result shows matched entries under “Failregex” (47 in this example). If zero matches appear but failed login attempts exist in the log, the filter regex may need adjustment or the log format differs from what the filter expects. Use the --print-all-matched flag to see exactly which lines triggered the filter.
View Detailed Jail Status and Ban Statistics
Check the detailed status of a specific jail to see current bans and failure counts. This status view helps you monitor attack patterns and verify that your jails are detecting threats correctly:
sudo fail2ban-client status sshd
The output displays current ban counts and the list of blocked IPs:
Status for the jail: sshd |- Filter | |- Currently failed: 1 | |- Total failed: 23 | `- File list: /var/log/auth.log `- Actions |- Currently banned: 3 |- Total banned: 8 `- Banned IP list: 198.51.100.45 203.0.113.78 192.0.2.100
“Currently failed” shows IPs approaching the ban threshold. “Currently banned” shows active bans. “Total” counts reflect all activity since the last service restart. Monitor these numbers regularly to identify attack trends and adjust thresholds accordingly.
Common Issues and Solutions
Jail fails to start: Check that the log file specified in logpath exists and is readable. Create the log file if missing, or disable the jail if the service is not installed.
Bans not working: Verify your firewall backend is installed and check that Fail2Ban created the rules. Debian 13 uses nftables by default, so check the nftables table first:
sudo nft list table inet f2b-table
Debian 11 and 12 use iptables by default. Check for the Fail2Ban chain:
sudo iptables -L f2b-sshd -n
If the chain or table does not exist, confirm that banaction in jail.local matches your firewall backend (nftables-multiport for Debian 13, iptables-multiport for Debian 11/12, or ufw if configured). Ensure the firewall service is running and starts on boot.
fail2ban-regex shows 0 matched: If fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf shows “0 matched” despite active SSH attacks, your system likely logs to the systemd journal instead of auth.log. This is common on Debian 11 and 12 when rsyslog is not running or configured to write authentication events to files. Check if your SSH failures appear in the journal:
journalctl -u ssh --since "1 hour ago" | grep -i "failed\|invalid"
If failures appear in the journal but not in auth.log, add backend = systemd to the [DEFAULT] section in jail.local and restart Fail2Ban. Debian 13 configures this automatically, but Debian 11 and 12 require manual configuration. See the “Configure Log Backend for Systemd Journal” section for the complete setup.
SSH connection attempts not being banned (kex_exchange_identification errors): If your logs show repeated kex_exchange_identification: Connection closed by remote host errors but Fail2Ban is not banning the IPs, you are seeing pre-authentication connection failures. These occur when clients disconnect during the SSH key exchange phase before any password attempt. The standard sshd filter only catches authentication failures like “Failed password” or “Invalid user”, not pre-auth disconnections.
Check your logs for this pattern:
error: kex_exchange_identification: Connection closed by remote host Connection closed by 192.168.1.100 port 41258
To catch these pre-auth failures, create a custom filter at /etc/fail2ban/filter.d/sshd-kex.local:
sudo tee /etc/fail2ban/filter.d/sshd-kex.local > /dev/null << 'EOF'
[INCLUDES]
before = common.conf
[Definition]
_daemon = sshd
failregex = ^%(__prefix_line)sConnection closed by port \d+\s*$
ignoreregex =
EOF
Then add a new jail in jail.local:
[sshd-kex] enabled = true filter = sshd-kex port = ssh backend = systemd maxretry = 5 bantime = 1h findtime = 10m
Restart Fail2Ban and verify the new jail is active with sudo fail2ban-client status sshd-kex. This jail runs alongside the standard sshd jail. Use maxretry = 5 or higher since legitimate clients occasionally disconnect during key exchange due to network issues.
Too many false positives: Increase maxretry, extend findtime, or add legitimate IPs to ignoreip. Review the filter’s failregex to ensure it matches only actual attacks. Consider monitoring logs for a few days before enabling aggressive ban settings in production.
Database corruption after crash: If Fail2Ban fails to start with database errors like:
ERROR Failed to initialize database SQLite database is malformed
Remove the database file and restart:
sudo rm /var/lib/fail2ban/fail2ban.sqlite3
sudo systemctl restart fail2ban
Fail2Ban recreates the database automatically on startup. Existing bans are lost but the service resumes normal operation. To prevent database corruption, avoid forcibly killing the Fail2Ban process during normal operation.
Service fails to start after upgrade: Check the systemd journal for detailed error messages:
sudo journalctl -u fail2ban -n 50 --no-pager
This shows the last 50 log entries for Fail2Ban, revealing Python errors, permission issues, or configuration problems that prevent startup. Review these messages carefully when troubleshooting startup failures.
Remove Fail2Ban
To remove Fail2Ban from your system, follow these steps:
Stop and Disable the Fail2Ban Service
Disable and stop the Fail2Ban service:
sudo systemctl disable fail2ban --now
This command disables the Fail2Ban service immediately (–now flag) and ensures it will not start automatically on future system reboots.
Uninstall Fail2Ban Package
Remove the Fail2Ban package:
sudo apt remove fail2ban
Remove orphaned dependencies that were installed with Fail2Ban:
sudo apt autoremove
This cleans up packages like python3-systemd, python3-pyinotify, and other Python dependencies that Fail2Ban required but are no longer needed.
Remove Configuration Files and Database
The apt remove command leaves configuration files intact. To completely remove Fail2Ban including all configuration files, use purge instead:
sudo apt purge fail2ban
The following command permanently deletes the Fail2Ban database, which contains ban history, failure counts, and persistent ban records. This action cannot be undone. Only proceed if you no longer need this data.
To remove the Fail2Ban database directory:
sudo rm -rf /var/lib/fail2ban
Verify Removal
Confirm that Fail2Ban was completely removed by refreshing the package cache and checking the package status:
sudo apt update
apt-cache policy fail2ban
The output confirms the package is no longer installed:
fail2ban:
Installed: (none)
Candidate: 1.1.0-8
Version table:
1.1.0-8 500
500 http://deb.debian.org/debian trixie/main amd64 Packages
Conclusion
Fail2Ban provides immediate protection against brute-force attacks and automated intrusion attempts by monitoring logs and dynamically updating firewall rules. Debian 13 uses nftables by default, while Debian 11 and 12 default to iptables, but you can configure any backend to match your environment. With the sshd jail enabled out of the box, SSH protection is active immediately after installation. Customize ban times, retry thresholds, and whitelists to balance security with usability, and extend protection to web servers, mail services, and other network applications as needed.
When i do a test i get this result. I see every 30 seconds someone try to login with random ports on SSH. The test result looks like it ignore it and not block the IP numbers
root@mail:/var/log# sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf Running tests ============= Use failregex filter file : sshd, basedir: /etc/fail2ban Use maxlines : 1 Use datepattern : {^LN-BEG} : Default Detectors Use log file : /var/log/auth.log Use encoding : utf-8 Results ======= Prefregex: 5716 total | ^(?P<mlfid>(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\S+\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?sshd(?:\(\S+\))?[\]\)]?:?|[\[\(]?sshd(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+)?(?:\[ID \d+ \S+\]\s+)?)(?:(?:error|fatal): (?:PAM: )?)?(?P<content>.+)$ `- Failregex: 1308 total |- #) [# of hits] regular expression | 19) [2] ^<F-NOFAIL>Received <F-MLFFORGET>disconnect</F-MLFFORGET></F-NOFAIL> from <HOST>(?: (?:port \d+|on \S+)){0,2}:\s*11: | 20) [1304] ^<F-NOFAIL><F-MLFFORGET>(Connection (?:closed|reset)|Disconnected)</F-MLFFORGET></F-NOFAIL> (?:by|from)(?: (?:invalid|authenticating) user <F-USER>\S+|.*?</F-USER>)? <HOST>(?:(?: (?:port \d+|on \S+|\[preauth\])){0,3}\s*|\s*)$ | 21) [2] ^<F-MLFFORGET><F-MLFGAINED>Accepted \w+</F-MLFGAINED></F-MLFFORGET> for <F-USER>\S+</F-USER> from <HOST>(?:\s|$) `- Ignoreregex: 0 total Date template hits: |- [# of hits] date format | [5716] {^LN-BEG}ExYear(?P<_sep>[-/.])Month(?P=_sep)Day(?:T| ?)24hour:Minute:Second(?:[.,]Microseconds)?(?:\s*Zone offset)? `- Lines: 5716 lines, 1308 ignored, 0 matched, 4408 missed [processed in 0.36 sec] Ignored line(s): too many to print. Use --print-all-ignored to print all 1308 lines Missed line(s): too many to print. Use --print-all-missed to print all 4408 linesThanks for the detailed output, Peter. The fail2ban-regex results explain exactly what is happening. Your output shows 0 matched lines for actual failed login attempts, while the 1308 “ignored” lines are connection disconnects and successful logins (patterns marked with
<F-NOFAIL>flags that Fail2Ban tracks but does not count as failures).The most likely cause is that your system uses systemd-journald as the primary logging mechanism, and
/var/log/auth.logcontains stale or incomplete data. On Debian 12 and newer, SSH authentication events often go directly to the systemd journal rather than traditional log files.First, verify where your SSH failures are actually logged:
If you see failed login attempts there but not in
/var/log/auth.log, add the systemd backend to your jail.local:Then restart Fail2Ban:
After restarting, check that the sshd jail is detecting failures:
The “Currently failed” and “Total failed” counts should start increasing as the systemd backend reads directly from journald. I have updated the article with a new troubleshooting item for this exact issue and added a clarifying note after the SSH jail configuration explaining the backend differences between Debian versions. Thanks for reporting this, as your feedback directly improved the guide for other readers.
Here is the result: changed my domain and ops IP in this log to 192.168.121.121 my domain.com
/var/log/auth.log:
When changeing the grep:
output:
So the regex isn’t correct in my situation then?
Your logs tell the whole story, Peter. Those
kex_exchange_identification: Connection closed by remote hostmessages are pre-authentication connection failures, not login failures. The remote client disconnects during the SSH key exchange phase before any password attempt occurs.The standard sshd filter (even in aggressive mode) will not ban these IPs. Looking at your logs, each event produces two lines:
The first line triggers a failure match but contains no IP. The second line contains the IP but is marked as a non-failure helper in the default filter. You need a custom filter to catch these. Create it at
/etc/fail2ban/filter.d/sshd-kex.local:Then add a new jail in
/etc/fail2ban/jail.local:Restart Fail2Ban and verify the new jail is active:
This runs alongside your existing sshd jail. The standard jail catches password failures while sshd-kex catches the pre-auth connection drops you are seeing. All settings are customizable based on your environment:
maxretrycontrols how many connection drops trigger a ban (start with 5 since legitimate clients occasionally disconnect during key exchange),bantimesets how long IPs stay blocked, andfindtimedefines the window for counting failures. The every-30-seconds pattern from a single IP is clearly automated probing, so even conservative thresholds will catch it quickly.I get a error when I add the files and update jail.local
I checked the internet and this filter
gives me this result. Looks good right?
root@mail:/etc/fail2ban/filter.d# sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd-kex.local Running tests ============= Use failregex filter file : sshd-kex, basedir: /etc/fail2ban Use datepattern : {^LN-BEG} : Default Detectors Use log file : /var/log/auth.log Use encoding : utf-8 Results ======= Failregex: 1710 total |- #) [# of hits] regular expression | 2) [1710] ^(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\S+\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?sshd(?:\(\S+\))?[\]\)]?:?|[\[\(]?sshd(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+ )?(?:\[ID \d+ \S+\]\s+)?Connection closed by .*? `- Ignoreregex: 0 total Date template hits: |- [# of hits] date format | [7543] {^LN-BEG}ExYear(?P<_sep>[-/.])Month(?P=_sep)Day(?:T| ?)24hour:Minute:Second(?:[.,]Microseconds)?(?:\s*Zone offset)? `- Lines: 7543 lines, 0 ignored, 1710 matched, 5833 missed [processed in 0.29 sec] Missed line(s): too many to print. Use --print-all-missed to print all 5833 linesThis is fail2ban log:
The jail is working, Peter. Your log shows detections and a successful ban. The filter you found works fine.
Great tutorial, however your step to check if the fail2ban install went ok,”fail2ban –version”, throws a command not found error. “sudo fail2ban-client version” seems to be the correct form.
Checking the “systemctl status fail2ban” does work, once one has the jail.local correctly configured.
Again, great tutorial.
Thanks for catching that, RDK. You’re absolutely right, the version check command I provided was incorrect. The proper command is:
I’ve updated the “Confirm Fail2Ban Installation” section to use the correct syntax. The sudo prefix isn’t required for version checks since you’re just querying the client, not modifying system configuration. Thanks for testing the steps thoroughly and reporting the issue. Feedback like this helps keep the guide accurate for everyone.
Wow! Thanks a lot! I try to find the problem a lot of time!
Thanks for the feedback.
For D12, change “backend = auto” by “backend = systemd” in “/etc/fail2ban/jail.local” to make fail2ban start 😉
That’s great advice – just what I needed to overcome “code=exited, status=255/EXCEPTION” error.
Thanks to you, Steve and Joshua James for the article. Excellent!
Thanks for sharing this, Steve. You’re correct that on Debian 12, explicitly setting the backend to systemd in jail.local can resolve startup issues. Add this line to the [DEFAULT] section:
The systemd backend reads logs directly from the systemd journal instead of polling log files, which works more reliably on systems using journald. After adding this setting, restart Fail2Ban with systemctl restart fail2ban. Thanks for pointing this out for Debian 12 users.