Overview Mail Server Hardening
Fail2Ban Hardening Guide
_Last updated: 2026-01-01
This document is the authoritative, consolidated reference for Fail2Ban on the Mail2 system. It includes configuration, AbuseIPDB integration, Rspamd interaction, recidive behavior, diagnostics, verification, and operational procedures.
1. Scope and Goals
- Protect SSH, SMTP, SMTP AUTH, IMAP/POP3, and web authentication services
- Enforce network-level blocking using nftables
- Report high-confidence abuse to AbuseIPDB
- Maintain performance and avoid false positives
- Provide repeatable, auditable configuration
- Explain expected and unexpected behaviors
2. System Overview
- OS: Debian 13
- Mail stack: Postfix, Dovecot, Rspamd
- Web: Apache
- Firewall: nftables
- Logging: systemd journal +
/var/log/fail2ban.log - Abuse reporting: AbuseIPDB
3. Fail2Ban Configuration Layout
Main configuration files:
1
2
/etc/fail2ban/jail.conf
/etc/fail2ban/jail.local
Per-jail overrides:
1
/etc/fail2ban/jail.d/
Enabled jails:
- sshd (auto-enabled on Debian 13)
- apache-auth
- postfix
- postfix-sasl
- dovecot
- recidive
4. Debian 13 SSH Behavior
On Debian 13, the sshd jail:
- Is enabled automatically
- Is defined in
/etc/fail2ban/jail.local - Requires no
jail.d/sshd.conffile
Recommended override:
1
2
3
[sshd]
maxretry = 3
bantime = 12h
AbuseIPDB reporting for SSH is optional and often disabled to avoid noise.
5. Apache Authentication Jail
1
2
3
4
5
6
7
8
9
10
11
12
[apache-auth]
enabled = true
backend = systemd
findtime = 3m
maxretry = 5
bantime = 10m
journalmatch = _SYSTEMD_UNIT=apache2.service
action = %(action_mw)s
%(action_abuseipdb)s[abuseipdb_category="18"]
6. Postfix (SMTP Protocol Abuse)
1
2
3
4
5
6
7
8
9
10
11
12
[postfix]
enabled = true
backend = systemd
findtime = 10m
maxretry = 3
bantime = 1h
journalmatch = _SYSTEMD_UNIT=postfix.service
action = %(action_mw)s
%(action_abuseipdb)s[abuseipdb_category="22"]
7. Postfix SASL (SMTP AUTH)
1
2
3
4
5
6
7
8
9
10
11
12
[postfix-sasl]
enabled = true
backend = systemd
findtime = 10m
maxretry = 3
bantime = 4h
journalmatch = _SYSTEMD_UNIT=postfix.service
action = %(action_mw)s
%(action_abuseipdb)s[abuseipdb_category="18"]
8. Dovecot (IMAP / POP3)
1
2
3
4
5
6
7
8
9
10
11
12
[dovecot]
enabled = true
backend = systemd
findtime = 10m
maxretry = 4
bantime = 2h
journalmatch = _SYSTEMD_UNIT=dovecot.service
action = %(action_mw)s
%(action_abuseipdb)s[abuseipdb_category="18"]
9. Recidive Jail (Repeat Offenders)
1
2
3
4
5
6
7
[recidive]
enabled = true
backend = auto
logpath = /var/log/fail2ban.log
findtime = 1d
maxretry = 5
bantime = 7d
Important behavior:
- Recidive bans do not persist across Fail2Ban restarts
- An Unban shortly after a Ban usually indicates a restart
- This is expected and documented behavior
10. AbuseIPDB Configuration
Main action file:
1
/etc/fail2ban/action.d/abuseipdb.conf
1
2
abuseipdb_apikey = <REDACTED>
abuseipdb_category = 18
Verification:
1
fail2ban-client -d | grep abuseipdb
11. Rspamd Integration Philosophy
- Rspamd evaluates message content and reputation
- Fail2Ban blocks persistent abusive behavior
- Never ban based on spam score alone
Rspamd is the judge. Fail2Ban is the bouncer.
12. Logging Locations
Fail2Ban:
1
/var/log/fail2ban.log
Rspamd:
1
/var/log/rspamd/rspamd.log
Firewall:
1
nft list ruleset
13. Operational Cheat Sheet
Restart Fail2Ban:
1
systemctl restart fail2ban
Check jail status:
1
2
fail2ban-client status
fail2ban-client status postfix-sasl
Manual ban/unban:
1
2
fail2ban-client set sshd banip 1.2.3.4
fail2ban-client set sshd unbanip 1.2.3.4
Appendix E – Verification & Diagnostics
This appendix documents how to prove Fail2Ban is behaving correctly and how to diagnose unexpected behavior.
E.1 Confirm Configured Ban Time (Authoritative)
1
fail2ban-client get <jail> bantime
Examples:
1
2
3
4
5
6
fail2ban-client get apache-auth bantime
fail2ban-client get postfix bantime
fail2ban-client get postfix-sasl bantime
fail2ban-client get dovecot bantime
fail2ban-client get sshd bantime
fail2ban-client get recidive bantime
Interpretation (seconds):
- 600 = 10 minutes
- 3600 = 1 hour
- 14400 = 4 hours
- 43200 = 12 hours
- 604800 = 7 days
E.2 Verify Runtime Ban Duration (Ground Truth)
1
grep -E " \[<jail>\] (Ban|Unban) " /var/log/fail2ban.log
Example:
1
grep -E " \[apache-auth\] (Ban|Unban) " /var/log/fail2ban.log
Compare timestamps between Ban and Unban entries.
E.3 Check Active Bans and Remaining Time
1
2
fail2ban-client status <jail>
fail2ban-client get <jail> banip --with-time
Remaining time is shown in seconds.
E.4 Recidive Verification
Confirm recidive is file-backed:
1
fail2ban-client status recidive
Expected:
1
File list: /var/log/fail2ban.log
Check recidive ban history:
1
grep -E " \[recidive\] (Ban|Unban) " /var/log/fail2ban.log
E.5 Explain Unexpected Unbans
Unexpected early Unbans are almost always caused by a Fail2Ban restart.
Confirm restarts:
1
journalctl -u fail2ban --since "YYYY-MM-DD HH:MM" --until "YYYY-MM-DD HH:MM"
If a restart occurred during a ban window, the Unban is expected.
E.6 Firewall Enforcement Validation
1
nft list ruleset | grep -i fail2ban -A5
- IP present → ban active
- IP missing → ban expired or service restarted
E.7 Pre-Restart Configuration Validation
Always validate configuration before restarting Fail2Ban:
1
fail2ban-client -d >/dev/null
- Silent return = configuration valid
- Output = fix errors before restarting
E.8 Key Operational Truths
- Configuration values ≠ runtime behavior
- Logs are the source of truth
- Recidive bans do not survive restarts
- Normal jails may log Restore Ban events
- Restart history explains most anomalies