This is an old revision of the document!
fail2ban
This tutorial is designed to help you install fail2ban and get a basic set of configurations in place.
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local sudo nano /etc/fail2ban/jail.local
Once inside the configuration file jail.local edit the destination email and the action parameter. Read the conf file and decide which combination of m, w, l is right for your situation.
<destemail = email> <action = %(action_mwl)s>
Default policy targets the middle in this example. Later, jails like sshd or recidive are stricter and get maxretry = 1 overrides, where apache or public servers are overridden to maxretry = 5 for more tolerance:
[DEFAULT] bantime = 1h findtime = 30d maxretry = 3
Increase db purge age so as to retain enough for the jail parameters:
sudo nano /etc/fail2ban/fail2ban.conf <dbpurgeage = 30d>
Add enabled = true to desired jails. Here's an example of me setting ssh to something very strict. Only do this in tightly monitored and low access scenarios, or you will have a lot of false positives from user error:
[sshd] enabled = true port = ssh logpath = %(sshd_log)s backend = %(sshd_backend)s maxretry = 1
The repeat offender, or recidivist jail, is listed under [recidive] and I give it particular attention. In a tightly controlled environment, if someone has banged once on ssh invalidly and does it again, they have no reason to bang again indefinitely. Again, in larger environments, it might not be possible to enforce maxretry = 1.
[recidive] enabled = true logpath = /var/log/fail2ban.log banaction = iptables-multiport[blocktype=DROP] banaction_allports = iptables-allports[blocktype=DROP] bantime = 100y maxretry = 1
Here's an example of keeping postfix more tolerant, so that you don't get false positives on more common services while users are setting up stuff or accessing public facing resources:
[apache-auth] enabled = true port = http,https logpath = %(apache_error_log)s maxretry = 5 #increased to 5
Once you activate desired jails, restart service or reload config:
sudo systemctl restart fail2ban.service sudo fail2ban-client reload
Hope this helps! Oh yeah … here is how to remove a false positive!
fail2ban-client set ssh unbanip 10.xx.15x.12x fail2ban-client unban --all
Another method that does more than individual services, and instead zaps all records:
sudo systemctl stop fail2ban sudo truncate -s 0 /var/log/fail2ban.log sudo rm /var/lib/fail2ban/fail2ban.sqlite3 sudo systemctl restart fail2ban
Systemd log issues. Change the sshd jail as follows
sudo nano /etc/fail2ban/jail.local backend = systemd #backend = %(sshd_backend)s
Some recommend adding backend = systemd into jail.conf, but I've found that does nothing. The error over ipv6 not being set and using auto can be removed as follows:
sudo nano /etc/fail2ban/fail2ban.conf 'allowipv6 = auto'
To check a particular jail's statistics:
sudo fail2ban-client status recidive
Install rpl and use it to change default banaction to DROP:
sudo apt install rpl sudo rpl -q 'banaction = iptables-multiport' 'banaction = iptables-multiport[blocktype=DROP]' /etc/fail2ban/jail.local && sudo rpl -q 'banaction_allports = iptables-allports' 'banaction_allports = iptables-allports[blocktype=DROP]' /etc/fail2ban/jail.local && sudo fail2ban-client reload
Small script / one-liner to avoid remembering iptables flags for jails I monitor a lot:
cat << 'EOF' > /usr/local/bin/list-recidive-ips.sh #!/bin/bash iptables -L f2b-recidive -v -n EOF chmod 750 /usr/local/bin/list-recidive-ips.sh
Change all reject rules to drop for a given iptables fail2ban managed jail/entry:
sudo iptables -L f2b-recidive -n --line-numbers | grep REJECT | awk '{print $1}' | sort -r | xargs -I {} sudo iptables -R f2b-recidive {} -j DROP
Script, fail2ban-stats.sh, which queries all jails for historical and current bans:
<bash code> #!/bin/bash # /usr/local/bin/fail2ban-stats.sh - improved version
echo “Jail | Banned now | Total failed | Total banned | Actions taken” echo “——————————|————|————–|————–|————–”
# Get list of jails dynamically (better than hardcoding) jails=$(sudo fail2ban-client status | grep “Jail list” | sed 's/.*Jail list:' | tr -d ' ' | tr ',' ' ') for jail in $jails; do stats=$(sudo fail2ban-client status “$jail” 2>/dev/null) if [ -z “$stats” ]; then printf “%-30s | inactive or error\n” “$jail” continue fi # Safer extraction without variable-length lookbehind banned=$(echo “$stats” | awk '/Currently banned:/ {print $NF}' || echo 0) failed=$(echo “$stats” | awk '/Total failed:/ {print $NF}' || echo 0) tbanned=$(echo “$stats” | awk '/Total banned:/ {print $NF}' || echo 0) actions=$(echo “$stats” | awk '/Actions executed:/ {print $NF}' || echo 0) # or “Actions taken” if your version says that printf “%-30s | %10s | %12s | %12s | %12s\n” “$jail” “$banned” “$failed” “$tbanned” “$actions” done </code> — oemb1905 2026/03/21 21:58