Guides
fail2ban for Postfix and Dovecot
Mail servers get hammered by SMTP AUTH and IMAP password guessing around the clock. Three stock jails, postfix, postfix-sasl and dovecot, stop most of it. This guide covers the config, the filter modes and the journal setup that current Debian and Ubuntu releases need.
To protect Postfix and Dovecot with fail2ban, enable three stock jails in /etc/fail2ban/jail.local: [postfix-sasl] for SMTP AUTH password guessing, [dovecot] for IMAP/POP3 logins and [postfix] for rejected SMTP clients. On Debian 12/13 and Ubuntu 24.04 mail logs live in the systemd journal, so add backend = systemd. Check with sudo fail2ban-client status postfix-sasl.
[postfix-sasl]
enabled = true
backend = systemd
maxretry = 3
[dovecot]
enabled = true
backend = systemd
[postfix]
enabled = true
backend = systemdThe stock jails already know the ports, the filters and the journal units. You only switch them on and pick a backend.
Which mail jails fail2ban ships#
All of these are defined in jail.conf and are disabled until you enable them:
| Jail | Filter | Catches | Banned ports |
|---|---|---|---|
postfix-sasl |
postfix[mode=auth] |
SASL LOGIN/PLAIN authentication failed |
smtp, 465, submission, imap, imaps, pop3, pop3s |
postfix |
postfix (mode more) |
SMTP rejects: relay denied, unknown users, bad HELO, RBL hits | smtp, 465, submission |
postfix-rbl |
postfix[mode=rbl] |
Clients refused because they're on a DNS blocklist (maxretry = 1) |
smtp, 465, submission |
dovecot |
dovecot |
Failed IMAP, POP3, ManageSieve and submission logins | pop3, pop3s, imap, imaps, submission, 465, sieve |
sieve |
sieve |
Cyrus timsieved login failures (not Dovecot) |
smtp, 465, submission |
postfix-sasl bans the IMAP and POP3 ports too, because the same password is usually valid for both protocols. An attacker who fails SMTP AUTH shouldn't get a fresh start on IMAP.
The old filter.d/postfix-sasl.conf and postfix-rbl.conf files no longer exist. They became modes of the single postfix filter, so filter = postfix-sasl in an old guide now fails with a "filter not found" error. Use the jail names above and let jail.conf pick the filter.
Step by step: Postfix and Dovecot on Debian or Ubuntu#
- Install fail2ban and the journal bindings.
sudo apt install fail2ban python3-systemd. Debian 12 and 13 don't install rsyslog, so/var/log/mail.logdoesn't exist and the journal is the only log source. - Create the jails. Put the block from the top of this page in
/etc/fail2ban/jail.local, and setignoreipto your office or VPN network so a misconfigured phone doesn't lock out the boss. - Check the config.
sudo fail2ban-client -tparses everything and reports errors without touching the running service. - Reload.
sudo systemctl restart fail2ban, orsudo fail2ban-client reloadif it's already running. - Confirm the jails are running.
sudo fail2ban-client statusshould listdovecot, postfix, postfix-sasl.
fail2ban 1.1.1's paths-debian.conf already sets postfix_backend = systemd, so on 1.1.1 the Postfix jails use the journal without being told. The packaged 1.0.2 (Debian 12, Ubuntu 24.04) and 1.1.0 (Debian 13, Ubuntu 26.04) don't, which is why the explicit backend = systemd lines are there. They're harmless on 1.1.1.
The filters bring their own journal matches. Postfix reads _SYSTEMD_UNIT=postfix.service and postfix@-.service (Debian runs the real daemon as postfix@-), and Dovecot reads _SYSTEMD_UNIT=dovecot.service.
The postfix-sasl jail (SMTP AUTH brute force)#
This is the jail that matters most on a mail server. Bots try leaked passwords against port 587 and 465, and Postfix logs each failure like this:
Oct 25 18:36:10 mail postfix/submission/smtpd[11538]: warning: unknown[203.0.113.42]: SASL LOGIN authentication failed: UGFzc3dvcmQ6The auth mode of the filter matches SASL LOGIN, PLAIN, CRAM-MD5 and DIGEST-MD5 failures. It deliberately ignores Connection lost to authentication server, so a crashed Dovecot auth socket doesn't ban your own users. In 1.1.1 it also captures sasl_username= as the user when Postfix logs it.
A stricter setup for a server with few legitimate users:
[postfix-sasl]
enabled = true
backend = systemd
maxretry = 3
findtime = 1h
bantime = 1dBots rotate addresses slowly, so a longer findtime catches them, and the recidive jail escalates repeat offenders to a week.
"fail2ban postfix-sasl not working"#
The usual causes, in order:
- No log source.
Have not found any log file for postfix-sasl jailmeans/var/log/mail.logis missing. Addbackend = systemd(full fix). - The SMTP AUTH failures never reach the log. Postfix logs them only when SASL is handled by smtpd. If authentication happens on a separate submission proxy, the proxy's logs need their own jail.
- Wrong jail name or filter.
filter = postfix-saslpoints at a file that no longer exists. Use[postfix-sasl]fromjail.confwith nofilterline.
Test the filter against the journal directly:
sudo fail2ban-regex systemd-journal 'postfix[mode=auth]'The postfix jail and its modes#
The [postfix] jail uses mode = more by default, which combines normal and rbl. Set a different mode in the jail to change what it bans:
| Mode | What it matches |
|---|---|
normal |
reject: lines (relay access denied, unknown user, bad HELO, sender domain not found), too many errors |
rbl |
554 5.7.1 Service unavailable; Client host [...] blocked |
more (default) |
normal + rbl |
auth |
SASL authentication failures (used by postfix-sasl) |
ddos |
lost connection after CONNECT/EHLO, postscreen PREGREET/HANGUP, COMMAND ... LIMIT, connection rate limit exceeded |
extra |
auth + normal |
errors |
too many errors after ... only, for a ban on the first hit |
aggressive |
Everything above, plus hostname ... does not resolve to address |
[postfix]
enabled = true
backend = systemd
mode = aggressiveaggressive is effective against spam bots but can ban misconfigured legitimate senders, such as a small business mail server with broken reverse DNS. Start with the default, look at what gets banned for a week, then decide. If you use aggressive, you can drop the separate postfix-sasl jail, because the auth failures are already included.
The postfix-rbl jail#
postfix-rbl bans a client the first time Postfix rejects it for being on a DNS blocklist (maxretry = 1). It only works if Postfix itself checks blocklists, for example with reject_rbl_client zen.spamhaus.org in smtpd_recipient_restrictions, or with postscreen DNSBL scoring. Without that, the jail never matches anything.
[postfix-rbl]
enabled = true
backend = systemd
bantime = 1dThe benefit is mainly load: a listed bot is dropped at the firewall instead of costing a DNS lookup and an SMTP conversation on every retry.
The dovecot jail#
The dovecot filter catches failed logins on imap-login, pop3-login, managesieve-login and submission-login, plus PAM, SQL, LDAP and passwd-file authentication failures from auth-worker:
Oct 25 18:36:10 mail dovecot: imap-login: Aborted login (auth failed, 1 attempts in 2 secs): user=<info@example.com>, method=PLAIN, rip=203.0.113.42, lip=192.0.2.10, TLSDovecot isn't covered by paths-debian.conf's journal switch, so on systems without rsyslog either set backend = systemd or rely on 1.1.1's automatic fallback. 1.1.1 switches a jail to the journal when its log file is missing and its filter defines a journalmatch, which the dovecot filter does.
The filter has an aggressive mode that also bans clients disconnecting without trying to log in (no auth attempts, disconnected before auth was ready, disconnected during TLS handshake). That stops scanners, but the Dovecot authors of the filter warn it can catch misconfigured MTAs and clients:
[dovecot]
enabled = true
backend = systemd
mode = aggressiveDovecot 2.4 log format (Debian 13)#
Dovecot 2.4 changed its log lines. A failed IMAP login now looks like this:
Oct 25 18:36:10 mail dovecot[241]: imap-login: Login aborted: Connection closed (auth failed, 3 attempts in 16 secs) (auth_failed): user=<test>, method=PLAIN, rip=192.0.2.241, lip=203.0.113.104, TLS, session=<9ZHq02g3J8S60fan>Support for the 2.4 format arrived in fail2ban 1.1.1. Debian 13 trixie ships Dovecot 2.4 with fail2ban 1.1.0, whose dovecot filter doesn't match Login aborted. The jail starts, finds nothing, and never bans. If fail2ban-regex reports 0 matches on Dovecot 2.4, install the 1.1.1 filter as a local override:
sudo curl -o /etc/fail2ban/filter.d/dovecot.local \
https://raw.githubusercontent.com/fail2ban/fail2ban/1.1.1/config/filter.d/dovecot.conf
sudo fail2ban-regex systemd-journal dovecot
sudo systemctl restart fail2banA .local file is read after the .conf file and overrides it, so the package can still update dovecot.conf without conflicts. Delete the .local once your distribution ships 1.1.1 or newer. The 1.1.1 filter also contains the fix for the ReDoS issue GHSA-33wh-ccjc-p397.
Sieve, ManageSieve and webmail#
- Dovecot ManageSieve (port 4190) is already covered by the
dovecotfilter throughmanagesieve-login. Add4190to the jail's ports if clients reach it from outside:port = pop3,pop3s,imap,imaps,submission,465,sieve,4190. - The
sievejail is for Cyrustimsieved(badlogin: ... authentication failure), not Dovecot. Its stockportline lists SMTP ports; setport = 4190if you use it. - Roundcube has its own stock jail,
roundcube-auth, that reads Roundcube's error log. Webmail logins go through IMAP, so the dovecot jail sees them too, but from the webmail server's IP. Make sure that IP is inignoreipor fail2ban will ban your own webmail.
Test the mail jails#
Run the filters against what's already in the journal before trusting them:
sudo fail2ban-regex systemd-journal 'postfix[mode=auth]'
sudo fail2ban-regex systemd-journal 'postfix[mode=more]'
sudo fail2ban-regex systemd-journal dovecotIf you read files instead, pass the file: sudo fail2ban-regex /var/log/mail.log 'postfix[mode=auth]'. More options are on the fail2ban-regex page.
Then check that the jails count failures:
sudo fail2ban-client status postfix-sasl
sudo fail2ban-client status dovecotWith the journal backend, the File list: line in the status output is replaced by Journal matches: followed by the unit names. That's expected.
mailcow, Mail-in-a-Box, iRedMail and Zimbra#
All-in-one mail stacks usually bring their own banning. Adding a second fail2ban layer on top often fights it.
- mailcow doesn't use fail2ban. Its
netfilter-mailcowcontainer watches the Postfix, Dovecot, SOGo and web UI logs and bans at the firewall itself. Configure ban time, max attempts and the allowlist in the mailcow admin UI under the Fail2ban parameters. When "mailcow fail2ban not working" comes up, look at the netfilter container's logs (docker compose logs netfilter-mailcow), not at a host fail2ban. - Mail-in-a-Box installs fail2ban and drops its own jails into
/etc/fail2ban/jail.d/mailinabox.conf. Put your changes in a.localfile so the setup script doesn't overwrite them. - iRedMail installs fail2ban with jails for Postfix, Dovecot, Roundcube and SOGo in
/etc/fail2ban/jail.d/, plus an optional SQL-backed ban list. Edit those files rather than adding duplicate jails. - Zimbra has no stock fail2ban filter. Zimbra logs authentication failures in
/opt/zimbra/log/audit.logwith the client address in anoip=field, so a custom filter has to match that field rather than the proxy address. Build it with the filter generator and test it withfail2ban-regexagainst your own log before enabling it. Zimbra's own account lockout and DoS filter are worth enabling either way. - docker-mailserver includes fail2ban inside the container. Enable it with
ENABLE_FAIL2BAN=1and manage it withdocker exec <container> fail2ban-client, not the host's fail2ban.
postfix, dovecot and sieve filter reference#
The built-in filters this guide uses, exactly as fail2ban 1.1.1 ships them — with the log lines they match and a link to test them. Looking for something else? See Exim, Sendmail, Courier, Roundcube and the other mail filters, or the full filter library.
postfix Postfix MTA
The postfix filter bans hosts with rejected SMTP commands, failed SASL logins, RBL hits and unknown recipients in Postfix. File: /etc/fail2ban/filter.d/postfix.conf; jail [postfix]; log on Debian/Ubuntu: /var/log/mail.log; modes: aggressive, auth, ddos, errors, extra, more, normal, rbl.
Log lines it bans (from fail2ban's test suite):
Feb 21 09:21:54 xxx postfix/smtpd[14398]: NOQUEUE: reject: RCPT from example.com[192.0.43.10]: 450 4.7.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo=Jul 12 07:47:48 saturn postfix/smtpd[8738]: NOQUEUE: reject: RCPT from 1-2-3-4-example.com[1.2.3.4]: 554 5.7.1 <smtp@example.com>: Relay access denied; from=<john@example.com> to=<smtp@example.org> proto=SMTP helo=<198.51.100.17>Jul 18 23:12:56 xxx postfix/smtpd[8738]: NOQUEUE: reject: RCPT from foo[192.51.100.65]: 554 5.7.1 <bad.domain>: Helo command rejected: match bad.domain; from=<foo@good.domain> to=<foo@porcupine.org> proto=SMTP helo=<bad.domain>
Show the failregex fail2ban compiles (2 lines)
^(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\S+\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?postfix\b([^\[\s]+)?(?:\(\S+\))?[\]\)]?:?|[\[\(]?postfix\b([^\[\s]+)?(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+)?(?:\[ID \d+ \S+\]\s+)?(?:\w+: )?(?:(?:milter-)?reject:|(?:improper command pipelining|too many errors) after \S+) <F-CONTENT>.+?</F-CONTENT>(?:, sasl_username=<F-USER>\S+</F-USER>)?\s*$^[A-Z]{4,} from [^[]*\[<HOST>\](?::\d+)?: [45][50][04] [45]\.\d\.\d+ (?:(?:<[^>]*>)?: )?(?:(?:Helo command|(?:Sender|Recipient) address) rejected: )?(?:Service unavailable|Access denied|(?:Client host|Command|Data command) rejected|Relay access denied|Malformed DNS server reply|(?:Host|Domain) not found|need fully-qualified hostname|match|[Uu](?:ser unknown|ndeliverable address))\b
^from [^[]*\[<HOST>\](?::\d+)?:?Test postfix in the browser or on the server: sudo fail2ban-regex /var/log/mail.log postfix · source
dovecot Dovecot IMAP/POP3
The dovecot filter bans failed IMAP, POP3 and SASL logins logged by Dovecot. File: /etc/fail2ban/filter.d/dovecot.conf; jail [dovecot]; log on Debian/Ubuntu: /var/log/mail.log; modes: aggressive, normal.
Log lines it bans (from fail2ban's test suite):
@400000004c91b044077a9e94 imap-login: Info: Aborted login (auth failed, 1 attempts): user=<martin@waschbuesch.de>, method=CRAM-MD5, rip=80.187.101.33, lip=80.254.129.240, TLS@400000004c91b044077a9e94 dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=web rhost=176.61.140.224@400000004c91b044077a9e94 dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=rhost=1.2.3.4 rhost=192.0.43.10
Show the failregex fail2ban compiles (4 lines)
^(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\w+(?:[\-\.]\w+)*\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?(?:dovecot(?:-auth)?|auth)(?:\(\S+\))?[\]\)]?:?|[\[\(]?(?:dovecot(?:-auth)?|auth)(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+)?(?:\[ID \d+ \S+\]\s+)?(?:(?:dovecot: )?auth(?:-worker)?(?:\((?:\S*,<ADDR>(?:,\S*)?|[^\)]+)\))?(?:<[^>]+>)?: )?(?:pam_unix(?:\(dovecot:auth\))?: |(?:pop3|imap|managesieve|submission)-login: )?(?:request \[\d+\]: )?(?:Info: )?(?:[a-z\-]+: )?(?:conn \w+:auth(?:-worker)? \([^\)]+\): auth(?:-worker)?<\d+>: )?<F-CONTENT>.+</F-CONTENT>$^authentication failure; logname=<F-ALT_USER1>\S*</F-ALT_USER1> uid=\S* euid=\S* tty=dovecot ruser=<F-USER>\S*</F-USER> rhost=<ADDR>(?:\s+user=<F-ALT_USER>\S*</F-ALT_USER>)?\s*$
^(?:Login aborted|Aborted login|Disconnected|Remote closed connection|Client has quit the connection)(?::(?: [^\( ]*|\([^\(]*\))*)? \((?:auth failed, \d+ attempts(?: in \d+ secs)?|tried to use (?:disabled|disallowed) \S+ auth|proxy dest auth failed)\)[^:]*:(?: user=<<F-USER>[^>]*</F-USER>>,)?(?: method=\S+,)? rip=<ADDR>(?:[^>]*(?:, session=<\S+>)?)\s*$
^pam\(\S+,<ADDR>(?:,\S*)?\): pam_authenticate\(\) failed: (?:User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \([Pp]assword mismatch\?\)|Permission denied)\s*$
^(?:[a-z\-]{3,15}\(\S*,<ADDR>(?:,\S*)?\): )?(?:[Uu]nknown user|[Ii]nvalid credentials|[Pp]assword mismatch)Test dovecot in the browser or on the server: sudo fail2ban-regex /var/log/mail.log dovecot · source
sieve Dovecot ManageSieve
The sieve filter bans failed ManageSieve logins. File: /etc/fail2ban/filter.d/sieve.conf; jail [sieve]; log on Debian/Ubuntu: /var/log/mail.log.
Log lines it bans (from fail2ban's test suite):
Dec 1 20:36:56 mail sieve[23713]: badlogin: example.com[1.2.3.4] PLAIN authentication failureJul 18 17:21:58 ophelia cyrus/timsieved[12305]: badlogin: example.com[1.2.3.4] PLAIN authentication failureSep 25 22:07:38 web9 timsieved[21040]: badlogin: web4[1.2.3.4] OTP authentication failure
Show the failregex fail2ban compiles (1 line)
^(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\S+\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?(?:cyrus/)?(?:tim)?sieved?(?:\(\S+\))?[\]\)]?:?|[\[\(]?(?:cyrus/)?(?:tim)?sieved?(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+)?(?:\[ID \d+ \S+\]\s+)?badlogin: \S+ ?\[<HOST>\] \S+ authentication failure$Test sieve in the browser or on the server: sudo fail2ban-regex /var/log/mail.log sieve · source
FAQ#
How do I configure fail2ban for Postfix?#
Enable the stock [postfix-sasl] and [postfix] jails in /etc/fail2ban/jail.local with enabled = true, add backend = systemd on Debian 12/13 or Ubuntu 24.04, and restart fail2ban. postfix-sasl stops SMTP AUTH password guessing; postfix bans clients Postfix rejects. Check with fail2ban-client status postfix-sasl.
Why is fail2ban postfix-sasl not working?#
Usually there's no log for it to read: Debian 12/13 have no /var/log/mail.log, so set backend = systemd. Also check that you didn't set filter = postfix-sasl, because that filter file was replaced by postfix[mode=auth]. Then test with fail2ban-regex systemd-journal 'postfix[mode=auth]'.
Does fail2ban work with Dovecot 2.4?#
Yes, from fail2ban 1.1.1, which added the 2.4 Login aborted ... (auth failed ...) format. fail2ban 1.1.0 on Debian 13 misses those lines. Install the 1.1.1 dovecot.conf as /etc/fail2ban/filter.d/dovecot.local and verify with fail2ban-regex systemd-journal dovecot.
What does the postfix-rbl jail do?#
It bans an SMTP client on the first rejection for being on a DNS blocklist, using filter = postfix[mode=rbl] and maxretry = 1. It only matches if Postfix itself checks blocklists, for example with reject_rbl_client or postscreen. Its job is to save DNS lookups and connections from known bad hosts.
Should I enable fail2ban on mailcow?#
No. mailcow ships its own banning container (netfilter-mailcow) that reads the mail logs and blocks at the firewall. Configure it in the mailcow admin UI. A host fail2ban can still protect SSH, but it shouldn't duplicate the mail jails.
Which postfix mode is best for fail2ban?#
Keep the default more (normal + rbl) for the postfix jail and run postfix-sasl alongside it. Move to aggressive only after checking a week of bans. It adds connection-level and DNS checks that stop more bots but can catch legitimate servers with broken DNS.
Spotted something wrong or out of date? Tell us — every page is checked against the current fail2ban release.