You don’t notice mail auth brute force when it’s small. You notice it when your IMAP log becomes a denial-of-service device, your CPU fans audition for a drone job, and users swear “nothing changed” while Outlook retries the same password forever.
Fail2ban can help. It can also hurt—silently—by banning your CEO in the middle of a board call because their phone’s mail app cached a bad password for eight hours. The difference is not “install fail2ban.” The difference is rules, log plumbing, and a rollout that respects production reality.
What you’re actually defending against
Mail servers attract attackers for boring reasons: credentials are valuable, SMTP is ubiquitous, and password reuse is a renewable energy source. The highest-volume “attacks” you’ll see are usually not sophisticated. They’re noisy, repetitive, and profitable because operators bet on one weak account in a thousand.
Common mail attack patterns worth banning
- IMAP/POP authentication brute force (Dovecot, Courier): repeated login failures against real usernames.
- SMTP AUTH brute force (Postfix SASL, Exim): repeated SASL auth failures, often from botnets.
- Web admin/UI brute force (Roundcube, Rspamd WebUI, mailcow admin, custom panels): password guessing and session probing.
- SMTP protocol abuse: invalid HELOs, pipelining abuse, “RCPT TO” dictionary attacks. Sometimes it’s spam-scanning; sometimes it’s a prelude to abuse.
- Credential stuffing: not many tries per IP. This is where naive fail2ban configurations fail, because each IP stays below your threshold.
What fail2ban is good at (and what it isn’t)
Fail2ban excels at rate-limiting by IP based on logs. If your attacker uses one IP to hammer auth, you can cut it off quickly. If they spread attempts across thousands of IPs, fail2ban becomes less decisive; then you need upstream controls (greylisting, reputation, rate limits, CAPTCHAs on web UIs, MFA, or provider-level filtering).
Also: fail2ban is not a substitute for correct mail security. You still need strong password policies, MFA where possible, disabling plaintext auth where you can, and sane exposure (don’t publish admin panels to the internet if you can avoid it).
Interesting facts & history (mail attacks didn’t start yesterday)
- SMTP predates modern authentication. SMTP was designed in the early 1980s for cooperative networks; bolting on auth and TLS came much later.
- “Open relays” were once normal. In the 1990s, many MTAs relayed mail freely; spam turned that into a liability and drove widespread relay restrictions.
- Greylisting became popular in the early 2000s. It exploited the fact that legitimate MTAs retry delivery; many spam bots didn’t.
- SASL is older than many people think. The Simple Authentication and Security Layer dates back to the late 1990s and was designed to standardize auth across protocols.
- fail2ban emerged in the mid-2000s. It rode the wave of “log-driven automation” on Linux—simple, effective, and dangerously easy to misconfigure.
- Botnets shifted from single-source to distributed attacks. Today’s credential stuffing often uses many IPs with low attempt rates, reducing the value of naive IP bans.
- Journald changed the game for log parsing. When systems moved from flat files to structured logs, tooling that assumed /var/log/* had to adapt.
- IPv6 made “just ban the /24” obsolete. Attackers can rotate addresses in massive ranges, and careless IPv6 banning can nuke legitimate networks.
- Mail remains a top breach vector. Not because SMTP is magical, but because credentials unlock password resets, invoices, and internal trust.
Get the logs right first (or fail2ban is a placebo)
Fail2ban can only act on what it can see. Most “fail2ban doesn’t work” tickets boil down to one of three problems:
- The service isn’t logging auth failures in a parseable way.
- The logs are going somewhere fail2ban isn’t watching (journald vs files, containers, chroot, split syslog).
- The regex doesn’t match the real lines (because your distro’s log format differs from the author’s mental model).
Decide your backend: file or systemd/journald
On modern distros, you often want the systemd backend so you’re not chasing rotated files and permissions. But if your MTAs write to classic syslog files, file tailing is fine. Pick one per host and keep it consistent; hybrid configs are where troubleshooting goes to die.
Also: consider that mail systems are frequently split across containers or separate VMs. If Postfix runs in one container and Dovecot in another, fail2ban in a third container won’t see logs unless you forward them. Your “jail enabled” message means nothing if it’s staring into an empty room.
Joke #1: The only thing more reliable than a brute-force bot is an executive who “didn’t change anything” right before the outage.
Mail jails that catch real attacks
Below are jails that tend to work across real-world Postfix + Dovecot stacks, plus a couple of web-facing mail-adjacent targets. The goal isn’t “ban everything.” The goal is: ban high-confidence abuse quickly, and treat ambiguous signals with caution.
Baseline assumptions
- You run fail2ban 0.11+ (newer is better, especially for systemd backend improvements).
- Your firewall actions are consistent: nftables on modern Debian/Ubuntu, firewalld on RHEL-ish systems, iptables on older setups.
- Your mail services log the client IP in the same line as the failure (or in a correlated line that the filter supports).
Postfix SASL auth failures (SMTP AUTH brute force)
This jail bans on repeated SASL authentication failures, which are high-signal. It catches the classic “try 10 passwords for user@domain” attack quickly.
cr0x@server:~$ sudo sed -n '1,200p' /etc/fail2ban/jail.d/postfix-sasl.local
[postfix-sasl]
enabled = true
backend = systemd
filter = postfix[mode=auth]
maxretry = 6
findtime = 10m
bantime = 4h
bantime.increment = true
bantime.factor = 2
bantime.multipliers = 1 2 4 8 16 32
action = nftables-multiport[name=postfix-sasl, port="smtp,submission,submissions"]
journalmatch = _SYSTEMD_UNIT=postfix.service
Why this works: It’s tied to auth failures, not random protocol weirdness. It uses incremental bantime so repeat offenders get longer bans without permanently punishing someone fat-fingering a password once.
Dovecot auth failures (IMAP/POP brute force)
Dovecot logs are typically straightforward and include the remote IP. This makes a good jail—again, high-signal events.
cr0x@server:~$ sudo sed -n '1,200p' /etc/fail2ban/jail.d/dovecot.local
[dovecot]
enabled = true
backend = systemd
filter = dovecot
maxretry = 5
findtime = 10m
bantime = 6h
bantime.increment = true
bantime.factor = 2
bantime.multipliers = 1 2 4 8 16
action = nftables-multiport[name=dovecot, port="imap,imaps,pop3,pop3s"]
journalmatch = _SYSTEMD_UNIT=dovecot.service
Opinion: If you run IMAP/POP on the public internet, you want this jail. “We only have a few users” is not a strategy; it’s a scheduling method for your next incident.
Roundcube (webmail login brute force)
Roundcube’s logs vary by setup, and a lot of people forget to enable useful logging. Fail2ban can still help if you have consistent login failure lines in syslog or Roundcube logs.
cr0x@server:~$ sudo sed -n '1,200p' /etc/fail2ban/jail.d/roundcube.local
[roundcube]
enabled = true
backend = auto
filter = roundcube-auth
logpath = /var/log/roundcube/errors.log
maxretry = 5
findtime = 15m
bantime = 12h
action = nftables-multiport[name=roundcube, port="http,https"]
Key detail: Webmail brute force often comes from a small set of IPs, but credential stuffing may not. This jail helps with the former.
Rspamd WebUI (admin panel brute force)
If you expose Rspamd’s WebUI to the internet, you’re choosing pain. If you must, at least protect it.
cr0x@server:~$ sudo sed -n '1,200p' /etc/fail2ban/jail.d/rspamd.local
[rspamd]
enabled = true
backend = systemd
filter = rspamd
maxretry = 4
findtime = 10m
bantime = 24h
action = nftables-multiport[name=rspamd, port="http,https"]
journalmatch = _SYSTEMD_UNIT=rspamd.service
Recidive: catch the repeat offenders across all jails
Recidive watches the fail2ban log itself. If an IP gets banned repeatedly across time, it earns a longer ban. This is one of the few “set it and forget it” features that actually pays rent.
cr0x@server:~$ sudo sed -n '1,200p' /etc/fail2ban/jail.d/recidive.local
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
backend = auto
bantime = 7d
findtime = 1d
maxretry = 3
action = nftables-allports[name=recidive]
When not to ban: SMTP “rejected” noise
You’ll be tempted to ban on Postfix “NOQUEUE: reject” lines. Don’t start there. Many rejects are legitimate misconfigurations from real senders, and banning them can create weird deliverability failures that are hard to explain.
If you do implement a jail for protocol abuse, keep it conservative and monitor it like a hawk. Treat it as “reduce log spam,” not “security.”
Regex workbench: prove the match before you ban
Your filters are only as good as your regex. The practical method is:
- Collect representative log lines (good, bad, and weird).
- Use
fail2ban-regexagainst real logs. - Adjust until matches are correct and false positives are boringly rare.
- Only then enable the jail.
One quote that operations people keep relearning the hard way:
“Hope is not a strategy.” — paraphrased idea often attributed in operations circles
Joke #2: Regex is like a chainsaw: powerful, noisy, and you’ll eventually bleed if you wave it around unsupervised.
Practical tasks: commands, outputs, decisions (12+)
Task 1: Confirm fail2ban is running and not flapping
cr0x@server:~$ systemctl status fail2ban --no-pager
● fail2ban.service - Fail2Ban Service
Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled; preset: enabled)
Active: active (running) since Mon 2026-01-04 08:11:22 UTC; 2h 14min ago
Docs: man:fail2ban(1)
Main PID: 1047 (fail2ban-server)
Tasks: 5 (limit: 18963)
Memory: 64.3M
CPU: 2min 18.541s
What it means: “active (running)” with stable uptime suggests it’s not crashing or constantly restarting.
Decision: If it’s restarting or inactive, fix that first. No amount of regex tuning helps if the daemon is dead.
Task 2: List enabled jails (reality check)
cr0x@server:~$ sudo fail2ban-client status
Status
|- Number of jail: 4
`- Jail list: dovecot, postfix-sasl, recidive, rspamd
What it means: These are the only jails doing anything.
Decision: If your intended jail isn’t listed, it’s not enabled or it failed to load. Go read journalctl -u fail2ban.
Task 3: Check one jail’s counters
cr0x@server:~$ sudo fail2ban-client status postfix-sasl
Status for the jail: postfix-sasl
|- Filter
| |- Currently failed: 12
| |- Total failed: 981
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 7
|- Total banned: 103
`- Banned IP list: 198.51.100.23 203.0.113.14 203.0.113.77
What it means: “Currently failed” is the number of failures in the window; “Currently banned” is active bans.
Decision: If “Total failed” is climbing but bans are zero, your maxretry may be too high, findtime too short, or the filter matches but the action isn’t working.
Task 4: Verify firewall integration (nftables example)
cr0x@server:~$ sudo nft list ruleset | sed -n '1,120p'
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif "lo" accept
tcp dport { 22, 25, 465, 587, 993 } accept
}
}
table inet f2b-table {
set f2b-postfix-sasl {
type ipv4_addr
flags timeout
}
chain f2b-postfix-sasl {
ip saddr @f2b-postfix-sasl drop
}
}
What it means: There’s a fail2ban table/set for the jail. If it’s missing, the action didn’t apply rules.
Decision: If fail2ban says it banned IPs but nftables has no sets/chains, you’re using the wrong action or missing permissions/capabilities.
Task 5: Confirm Postfix is logging SASL failures where fail2ban can see them
cr0x@server:~$ sudo journalctl -u postfix.service -S "30 min ago" | grep -E "SASL|authentication failed" | tail -n 5
Jan 04 10:12:43 mail postfix/smtpd[22901]: warning: unknown[203.0.113.77]: SASL LOGIN authentication failed: authentication failure
Jan 04 10:12:45 mail postfix/smtpd[22901]: warning: unknown[203.0.113.77]: SASL LOGIN authentication failed: authentication failure
What it means: You have the lines you expect, with IPs visible.
Decision: If you don’t see these, your SASL auth may be in a different unit/log facility, or auth failures are suppressed. Fix logging before filters.
Task 6: Confirm Dovecot auth failure format
cr0x@server:~$ sudo journalctl -u dovecot.service -S "30 min ago" | grep -i "auth failed" | tail -n 3
Jan 04 10:06:12 mail dovecot: auth: passwd-file(jdoe,203.0.113.14): Password mismatch
Jan 04 10:06:15 mail dovecot: imap-login: Aborted login (auth failed, 1 attempts): user=<jdoe>, method=PLAIN, rip=203.0.113.14, lip=192.0.2.10, TLS
What it means: It includes rip= (remote IP), which is what many default filters key off.
Decision: If your lines don’t contain IP, you’ll need a different filter or different Dovecot logging settings.
Task 7: Test a filter against real logs before enabling it
cr0x@server:~$ sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/postfix.conf --print-all-matched
Running tests
=============
Use failregex file : /etc/fail2ban/filter.d/postfix.conf
Use log file : /var/log/auth.log
Results
=======
Failregex: 18 total
|- #) [# of hits] regular expression
| 1) [18] warning: .*?\[<HOST>\]: SASL .* authentication failed
`-
Lines: 12458 lines, 0 ignored, 18 matched, 12440 missed
What it means: The regex hits exactly the lines you expect. “Missed” is fine; it’s everything else.
Decision: If matches are zero, fix regex or logpath. If matches are huge, you may be matching benign lines and need to tighten.
Task 8: Dry-run your ban parameters with fail2ban-client get
cr0x@server:~$ sudo fail2ban-client get postfix-sasl maxretry
6
cr0x@server:~$ sudo fail2ban-client get postfix-sasl findtime
600
cr0x@server:~$ sudo fail2ban-client get postfix-sasl bantime
14400
What it means: Values are in seconds for some queries. 600 seconds = 10 minutes; 14400 = 4 hours.
Decision: If your environment has flaky mobile clients or NAT’d offices, consider slightly higher maxretry or a shorter bantime at first, then increase once false positives are under control.
Task 9: Inspect fail2ban logs for action errors (the silent killer)
cr0x@server:~$ sudo tail -n 50 /var/log/fail2ban.log
2026-01-04 10:12:49,381 fail2ban.actions [1047]: NOTICE [postfix-sasl] Ban 203.0.113.77
2026-01-04 10:12:49,512 fail2ban.actions [1047]: ERROR Failed to execute ban jail 'postfix-sasl' action 'nftables-multiport': returned 127
2026-01-04 10:12:49,513 fail2ban.actions [1047]: ERROR Invariant check failed. Trying again...
What it means: It “banned” in fail2ban’s mind, but the action failed to run (exit 127 often means missing binary or wrong command).
Decision: Fix the action tooling (nft command path, permissions, action definition) before trusting any ban counts.
Task 10: Check whether you’re banning a NAT (office) and hurting many users
cr0x@server:~$ sudo fail2ban-client status dovecot
Status for the jail: dovecot
|- Filter
| |- Currently failed: 2
| |- Total failed: 204
| `- File list: /var/log/mail.log
`- Actions
|- Currently banned: 1
|- Total banned: 9
`- Banned IP list: 198.51.100.10
What it means: One IP currently banned. If that IP is a corporate NAT, you just blocked everyone behind it.
Decision: If this happens, add a targeted ignore list for known office egress IPs, but only after verifying you’re not whitelisting an attacker hiding behind “office VPN.”
Task 11: Add an ignore IP safely (and keep it auditable)
cr0x@server:~$ sudo sed -n '1,120p' /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 192.0.2.0/24 198.51.100.10
What it means: These sources will never be banned by any jail.
Decision: Keep ignore lists small. Prefer whitelisting only fixed egress IPs you control and monitor. Don’t dump entire ISP ranges in here because one VIP complained.
Task 12: Unban an IP (and then figure out why it got banned)
cr0x@server:~$ sudo fail2ban-client set dovecot unbanip 198.51.100.10
1
What it means: “1” indicates success (one ban removed).
Decision: Unban, then immediately investigate: was it user error, credential stuffing, or a misbehaving client? If you don’t answer that, you’ll be back here tomorrow.
Task 13: Confirm your jail is watching the right source (systemd backend)
cr0x@server:~$ sudo fail2ban-client get postfix-sasl backend
systemd
cr0x@server:~$ sudo fail2ban-client get postfix-sasl journalmatch
_SYSTEMD_UNIT=postfix.service
What it means: The jail is reading logs from journald filtered to the Postfix unit.
Decision: If journalmatch is empty and your system is noisy, you may parse unrelated lines and generate nonsense bans. Add the match.
Task 14: Measure the attack rate to tune thresholds
cr0x@server:~$ sudo journalctl -u postfix.service -S "1 hour ago" | grep -c "SASL LOGIN authentication failed"
482
What it means: 482 failures per hour is not “background radiation.” That’s active abuse or a client meltdown.
Decision: If rate is high, keep findtime reasonably short (5–15 minutes) so bans trigger quickly. If rate is low and you fear false positives, lengthen findtime and raise maxretry.
Fast diagnosis playbook
When “mail is slow” or “people can’t log in” and you suspect fail2ban, you want speed. Not perfection. Here’s the shortest path to truth.
First: is fail2ban actually blocking traffic?
- Check active bans in the relevant jail (
fail2ban-client status <jail>). - Check firewall rules/sets exist and contain the banned IP (nftables/iptables output).
- If you use a reverse proxy or load balancer, confirm the source IP in logs is the real client, not the proxy.
Second: is the jail matching the right log lines?
- Grab one recent failure line from journald/syslog.
- Run
fail2ban-regexagainst it (or a small log slice) using the exact filter used by the jail. - Validate that the extracted host is correct (especially with IPv6, bracketed addresses, or “unknown” hostname wrappers).
Third: is it a client problem masquerading as an attack?
- Look for one user generating repeated failures from a single NAT IP.
- Check whether a password changed recently (helpdesk logs, directory sync, OAuth token revocation).
- If it’s mobile mail, assume stale credentials and aggressive retry logic until proven otherwise.
Fourth: is the “attack” distributed?
- Count unique IPs hitting auth failures in the last hour.
- If the number is huge and each IP is low-volume, fail2ban will only nibble at the edges.
- Shift effort to rate limits, upstream filtering, stronger auth, or temporarily disabling exposed auth endpoints you don’t need.
Common mistakes: symptom → root cause → fix
1) “fail2ban says banned, but the attacker keeps trying”
Symptom: Jail shows bans; logs keep showing auth failures from the same IP.
Root cause: Action misconfigured (wrong backend action, missing nft/iptables, firewalld not running), or bans apply to IPv4 while traffic is IPv6.
Fix: Verify firewall rule presence and that the chain is hooked into INPUT. Ensure your action supports inet/IPv6 where needed; use nftables inet table or dual-stack actions.
2) “Users behind an office NAT can’t check mail”
Symptom: Multiple users report IMAP/SMTP timeouts; one IP is banned.
Root cause: A single misconfigured client behind NAT triggers bans for everyone.
Fix: Increase maxretry slightly for Dovecot/Postfix auth, enable incremental bans, and add a tightly scoped ignoreip only if you can monitor and trust that egress.
3) “No bans ever happen, but logs show failures”
Symptom: Total failed stays zero; fail2ban logs are quiet.
Root cause: Wrong backend/logpath. Example: jail tails /var/log/auth.log, but your system logs to journald only.
Fix: Switch to backend = systemd with journalmatch, or ensure rsyslog writes the expected file.
4) “Bans explode after an upgrade”
Symptom: Thousands of bans, including legitimate clients.
Root cause: Log format changed (package update, new verbosity), and regex now matches more than intended.
Fix: Run fail2ban-regex on a sample of new logs; tighten expressions; add ignoreregex for known benign lines.
5) “We banned the load balancer”
Symptom: Everything breaks at once; logs show the same internal IP failing auth repeatedly.
Root cause: X-Forwarded-For not passed, or mail proxy hides client IP, so all failures appear from the proxy.
Fix: Fix logging to include real client IP, or run fail2ban at the edge where you can see clients. Don’t guess.
6) “recidive is banning good networks for a week”
Symptom: Long bans triggered after short bursts of failures.
Root cause: You tuned primary jails too aggressively, causing false bans; recidive magnified them.
Fix: Calm down primary jails (raise maxretry, shorten bantime), then re-enable recidive once false positives are rare.
7) “Mail submission from mobile networks is flaky”
Symptom: Random inability to connect from LTE, but Wi‑Fi works.
Root cause: Over-broad bans on SMTP rejects or protocol anomalies; mobile carriers share IP space and NAT aggressively.
Fix: Avoid banning on generic SMTP rejects. Ban on auth failures and explicit abuse, not on “client said weird HELO.”
Three corporate mini-stories (how this fails in real companies)
Mini-story 1: The incident caused by a wrong assumption
A mid-sized company migrated mail from a legacy VM to a new distro release. They copied the old fail2ban config verbatim: same jails, same log paths, same filters. They even celebrated: “It’s identical, so it’s safe.”
What changed was boring: logging moved to journald, and rsyslog no longer wrote /var/log/auth.log by default. Fail2ban tailed a file that existed, but was basically empty. Meanwhile, Postfix SASL failures piled up in journald, unseen.
The first signal wasn’t “our mail is under attack.” The first signal was CPU: Dovecot’s auth workers were busy rejecting logins all day. The second signal was an account takeover: one reused password, one compromised mailbox, and then a burst of outbound spam that got their IP reputation hammered.
The fix was not heroic. They switched to backend = systemd, added journalmatch per service, tested with fail2ban-regex, and suddenly bans started happening for the right reasons. The lesson wasn’t “use journald.” It was “never assume your logs are where they used to be.”
Mini-story 2: The optimization that backfired
A different org had a clever idea: reduce log volume. Their mail logs were massive, storage was “too expensive,” and someone decided to drop verbosity on auth failures. Less disk, fewer alerts, everybody wins.
Two weeks later, they noticed a new pattern: users intermittently locked out. Their directory service showed odd login attempts, but the mail hosts had insufficient detail to attribute sources reliably. Fail2ban bans became chaotic because the remaining logs didn’t consistently include the remote IP on failure lines.
They “optimized” further by lowering maxretry and increasing bantime to compensate. That’s how they ended up banning hotel Wi‑Fi NATs and conference networks for an entire day. Support tickets spiked. Executives got involved. You know the rest.
They reverted logging to include auth context, kept logrotate sane, and moved the cost discussion where it belonged: storage is cheaper than incidents. They then tuned fail2ban based on measured failure rates, not vibes.
Mini-story 3: The boring but correct practice that saved the day
A regulated company ran mail in a segmented environment: separate front-end SMTP, separate IMAP, and a management network. Their fail2ban config was unglamorous. Every jail was tested with fail2ban-regex during change review. Every whitelist entry had a ticket reference. Every action used a single firewall backend (nftables) across hosts.
One Friday night, a credential stuffing campaign hit their IMAP endpoint. It was distributed and low-and-slow per IP. Fail2ban didn’t stop all attempts, because it can’t magically ban what never crosses thresholds. But it did stop the noisy subset fast, which kept the service responsive and the logs readable.
The real win came from their “boring” dashboards: they tracked auth failure rates and unique source IP counts. Within an hour they recognized the signature of stuffing. They temporarily tightened rate limits at the edge, forced password resets for a small set of targeted accounts, and enabled additional MFA enforcement for webmail.
Monday morning was calm. No heroic all-nighter. The incident report was almost annoying in its simplicity: tested rules, consistent logging, and metrics. Operations success often looks like nobody noticing you did anything.
Checklists / step-by-step plan
Step 1: Establish visibility (before you ban)
- Pick your log source: journald or files. Don’t mix casually.
- Confirm you can see Postfix and Dovecot failure lines with IPs.
- Enable fail2ban logging to a known file (
/var/log/fail2ban.logis typical).
Step 2: Start with high-signal jails
- Enable Postfix SASL auth jail.
- Enable Dovecot auth jail.
- Optionally enable web UI jails (Roundcube, Rspamd) only if exposed.
Step 3: Tune thresholds with measured data
- Measure auth failures per hour and unique IP count.
- Set
maxretryto catch bots, not typos (start at 5–6). - Use
findtimeof 10–15 minutes to catch bursts. - Use incremental bantime to punish repeat abuse without permanent foot-guns.
Step 4: Roll out safely
- Enable one jail at a time.
- Watch bans for 24 hours.
- Audit the first dozen banned IPs: confirm they’re real abuse.
- Only then enable recidive.
Step 5: Make it survivable for on-call
- Document how to unban and where to check logs.
- Keep ignore lists minimal and reviewed.
- Ensure the firewall backend is consistent across hosts (nftables or firewalld; pick one).
FAQ
1) Should I run fail2ban on the mail server or on the firewall?
Run it where you can reliably see the real client IP and where a ban actually blocks traffic. If logs show a proxy IP, run fail2ban at the proxy/edge or fix logging.
2) What’s a reasonable starting point for maxretry/findtime/bantime?
For auth failures: maxretry=5–6, findtime=10m, bantime=4–12h, plus incremental bans. Then adjust based on observed false positives and attack rate.
3) Why do I see lots of “failed” but few bans?
Either failures are spread across many IPs (distributed attack), or your thresholds are too lenient. Measure unique IPs per hour; if it’s high, fail2ban won’t be a silver bullet.
4) Will fail2ban stop credential stuffing?
Partially. It stops the loud subset. For true stuffing (many IPs, few tries each), you need additional controls: rate limiting, MFA, better passwords, and sometimes upstream reputation filtering.
5) Should I ban on Postfix “NOQUEUE: reject” lines?
Not as your first move. Those lines include a lot of benign failures. Start with auth failures. If you later add protocol-abuse bans, keep them conservative and monitored.
6) How do I avoid banning whole offices behind NAT?
Use incremental bantime, keep maxretry slightly higher for IMAP if your users are mobile-heavy, and consider whitelisting only stable, owned egress IPs. Better: educate users and enforce MFA where possible.
7) Does IPv6 change anything?
Yes. Ensure your actions support IPv6 and that your filters extract IPv6 addresses correctly. Also avoid sloppy “ban a whole range” instincts; IPv6 addressing makes that risky fast.
8) Should I enable recidive?
Yes, but not on day one. First prove your base jails have low false positives. Recidive amplifies your choices—good or bad.
9) What’s the safest way to test new filters?
Use fail2ban-regex against real logs and review matched lines. Don’t deploy a filter you haven’t proven against your own log format.
10) How do I know if fail2ban is the bottleneck?
Usually it isn’t. The bottleneck is auth backends (LDAP/SQL), disk I/O from excessive logging, or CPU from TLS/auth workers. Fail2ban is often just the messenger—and sometimes the scapegoat.
Conclusion: next steps that won’t backfire
If you want fail2ban for mail to actually help, treat it like production code: observable, tested, and rolled out in stages.
- Verify logging first (service unit, log source, IP present on failure lines).
- Enable only high-signal jails (Postfix SASL, Dovecot auth) and prove matches with
fail2ban-regex. - Use incremental bantime to punish repeat abuse while reducing collateral damage.
- Validate the firewall action (nftables/iptables rules exist and are actually hooked into traffic).
- Add recidive last, once you trust your false positive rate.
- Write down the unban procedure and keep whitelists small and accountable.
Do those, and fail2ban becomes what it should be: not “security theater,” but a useful, low-maintenance bouncer for the most obvious troublemakers.