fail2ban


This tutorial is designed to help you install fail2ban and get a basic set of configurations in place. My logic is as follows:

My defaults give a very small 1 hour punishment for first time offense of 3 violations of any jail. Repeat offenders, however, get immediate life-bans. So, the default is very tolerant and the extreme is essentially for life punishment. Using this framework, you override individual services with maxretry = xx to fit that service's tolerance level. For example, email and web services get a value of 5-10, while ssh might get 1-3. For floods, bot attacks, etc., you will also need to adjust findtime = yy to fit the interval.

You will need to change this recipe to fit your needs. At the same time, there are certain aspects in this tutorial, that are just “always best to do” kind of things, so hopefully you recognize those! Be careful, and don't f2b yourself out of your instance too many times ;)

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 = 3

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-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:

sudo cat > /usr/local/bin/fail2ban-stats.sh << 'EOF'
#!/bin/bash
# /usr/local/bin/fail2ban-stats.sh

#header
echo "Jail                          | Banned now | Total failed | Total banned | Actions taken"
echo "------------------------------|------------|--------------|--------------|--------------"

# Get list of jails
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

  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)  

  printf "%-30s | %10s | %12s | %12s | %12s\n" "$jail" "$banned" "$failed" "$tbanned" "$actions"
done
EOF

sudo chmod 750 /usr/local/bin/fail2ban-stats.sh

A small script that I wrote before I knew how to write systemd units that checks and restarts the service:

sudo cat > /usr/local/bin/fail2ban-restart.sh << 'EOF'
#!/bin/bash
RESTART="/bin/systemctl restart fail2ban.service"
STATUS="/bin/systemctl status fail2ban.service"
SERVICE="fail2ban.service"
LOGFILE="/home/logs/fail2ban.log"
if $STATUS | grep -q -E 'failed|dead|inactive'; then
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Jonathan, fail2ban failed → restarting" >> "$LOGFILE"
    echo "----------------------------------------" >> "$LOGFILE"
    $RESTART >> "$LOGFILE" 2>&1
    echo "----------------------------------------" >> "$LOGFILE"
    # Send email with the log content
    mail -s "[fail2ban-restart] $(hostname -f) - $(date '+%Y-%m-%d %H:%M:%S')" \
        fail2ban@haacksnetworking.org < "$LOGFILE"
else
    # Optional: log successful check (uncomment if desired)
    # echo "[$(date '+%Y-%m-%d %H:%M:%S')] fail2ban is running OK" >> "$LOGFILE"
    exit 0
fi
exit 0
EOF
sudo chmod 750 /usr/local/bin/fail2ban-restart.sh

Here is another script that sends the fail2ban-stats report to an email of one's choosing:

sudo cat > /usr/local/bin/fail2ban-report.sh << 'EOF'
#!/bin/bash
DATE=$(date +"%Y%m%d-%H:%M:%S")
LOG="/home/logs/fail2ban-report.log"

# create log (touch is idempotent)
touch "$LOG"

# generate report
echo "Jonathan, at $(date), your fail2ban stats for $(hostname -f) were as follows:" > "$LOG"
/bin/bash /usr/local/bin/fail2ban-stats.sh >> "$LOG"

#mail log
mail -s "[$(hostname -f)]-fail2ban-stats-$(date)]" email@haacksnetworking.org < "$LOG"
rm "$LOG"
EOF

mkdir -p /home/logs
sudo chmod 750 /usr/local/bin/fail2ban-report.sh

Run it hourly:

0 * * * * /usr/bin/flock --nonblock /tmp/f2b-report.lock /bin/bash /usr/local/bin/fail2ban-report.sh > /dev/null 2>&1

Create a custom jail for postfix floods, for example:

cat << 'EOF' >> /etc/fail2ban/jail.local

[postfix-flood-attack]
enabled  = true
maxretry = 1
filter   = postfix-flood-attack
action   = iptables-multiport[name=postfix, port="http,https,smtp,submission,pop3,pop3s,imap,imaps,sieve", protocol=tcp]
logpath  = /var/log/mail.log

EOF

# 2. Create the filter definition
cat << 'EOF' > /etc/fail2ban/filter.d/postfix-flood-attack.conf
[Definition]
failregex = lost connection after AUTH from (.*)\[<HOST>\]
ignoreregex =
EOF

# Set permissions and reload the jail
chmod 750 /etc/fail2ban/jail.local /etc/fail2ban/filter.d/postfix-flood-attack.conf
systemctl restart fail2ban.service

oemb1905 2026/03/22 20:44