Shore Up
A doorman at a building entrance turning away a figure who has knocked too many times, while a familiar regular on a small trusted list walks straight through.
LinuxMailSecurity

Automatically Ban Abusive IPs in Postfix with Fail2ban

Ketan Aagja7 min read
No ratings yet

The standard, boring way to block IPs that hammer your mail server is Fail2ban. It watches the mail log, counts matching failures per source IP inside a time window, and when a source crosses a threshold it inserts a firewall rule to drop that IP for a while. You could write a bash script that greps the log and pipes IPs into nft, and I'll say where that fits at the end — but reinventing Fail2ban is more error-prone than configuring it, so that's what this guide does.

Before you run this

What this does: It installs and configures Fail2ban to read Postfix's log, and to ban any IP that exceeds a failure threshold (for example, 5 failed SASL logins in 10 minutes) by adding a rule to the host firewall (iptables or nftables). Bans are temporary and expire automatically.

Privileges: Everything here needs root — use sudo. Fail2ban runs as a service and manipulates the host firewall, so it inherently requires elevated rights.

Test first, and mind who you ban. The single biggest risk is banning yourself or a legitimate mail relay. Before you enable a jail:

  • Read the filter and jail config. Run fail2ban-regex (shown below) against your real log to see exactly what would match — this bans nothing, it only reports.
  • Put your own admin IPs and trusted networks in ignoreip before you start the service. If you administer this box over SSH from the same address range your firewall action covers, a wrong rule can lock you out.
  • Do this in a maintenance window on a test VM or a low-traffic server first, not blindly on your production MX.

Is it reversible? Yes. Bans are firewall entries, not permanent state. You can unban an IP at any time, and by default bans do not survive a Fail2ban restart. Keep a console / out-of-band session open (your hypervisor console, IPMI, or a second SSH session from a whitelisted address) while you test, exactly as you would when editing a firewall — because Fail2ban is editing your firewall. If something goes wrong, systemctl stop fail2ban flushes its rules and restores normal access.

I'm assuming Debian 12 (bookworm) or Ubuntu 22.04, with Postfix logging via rsyslog to /var/log/mail.log, and systemd. If your box has no rsyslog and logs only to the journal, see the backend note below.

Install Fail2ban

sudo apt update
sudo apt install fail2ban

The package ships a default /etc/fail2ban/jail.conf and a set of filters in /etc/fail2ban/filter.d/, including ready-made Postfix filters. Never edit jail.conf directly — it's overwritten on upgrade. All your changes go in jail.local, which overrides it.

Set your thresholds and whitelist

Create /etc/fail2ban/jail.local:

[DEFAULT]
# IPs and networks that are NEVER banned. Put your admin host and
# any trusted relay here. localhost is included so you don't ban yourself.
ignoreip = 127.0.0.1/8 ::1 203.0.113.10 198.51.100.0/24

# How long an offender stays banned once tripped.
bantime  = 1h

# The window in which failures are counted.
findtime = 10m

# Failures allowed within findtime before a ban. This is your threshold.
maxretry = 5

Replace 203.0.113.10 and 198.51.100.0/24 with your real admin IP and trusted network. Those are obvious placeholders — do not leave them in.

bantime, findtime, and maxretry accept the human-readable time suffixes shown (m, h, d). Together maxretry + findtime are your log threshold.

Enable the Postfix jails

Append the jails to the same jail.local. Debian's default jail.conf already defines [postfix] and [postfix-sasl] with the correct filter, port, and log path, so enabling them is usually all you need:

[postfix]
enabled = true

[postfix-sasl]
enabled = true
  • postfix catches things like rejected RCPT floods and unknown-user probes.
  • postfix-sasl catches failed SMTP AUTH — the classic password-guessing attack.

If you want different thresholds per jail (say, a stricter one for SASL), add the override under that jail's section, e.g. a lower maxretry under [postfix-sasl].

Log backend: if your server has no rsyslog and Postfix logs only to the journal, /var/log/mail.log won't exist and the default logpath won't match. In that case set backend = systemd under [DEFAULT] and remove the logpath reliance. Confirm the exact backend values and journal-matching behaviour for your version in the Fail2ban man page (man jail.conf) rather than guessing — the details differ between releases.

Dry-run the filter before you start

This is the step that saves you. fail2ban-regex runs a filter against a log and tells you how many lines matched and which IPs it would ban — without touching the firewall:

# Test the SASL filter against your real mail log.
sudo fail2ban-regex /var/log/mail.log /etc/fail2ban/filter.d/postfix-sasl.conf

Read the summary. If it reports zero matches on a log you know contains failed logins, your log path or filter is wrong — fix that before enabling anything. If it matches lines you don't consider abuse, your threshold or filter needs tuning. Do the same for postfix.conf.

Start it

sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban

If status shows the service active and no errors, Fail2ban is now watching the log.

Verify it's working

Check the running jails and their counters:

sudo fail2ban-client status
sudo fail2ban-client status postfix-sasl

The per-jail output shows currently failed and currently banned IPs, plus totals. To see the actual firewall entries Fail2ban added (which chain and how depends on whether your box uses iptables or nftables):

sudo iptables -S | grep -i f2b        # iptables systems
sudo nft list ruleset | grep -i f2b   # nftables systems

You'll see a Fail2ban chain (named f2b-...) populated as IPs trip the threshold. Watch the log live during a real attack, or trigger it yourself from a non-whitelisted test host by making more than maxretry failed AUTH attempts within findtime, then re-check fail2ban-client status postfix-sasl.

Undo: unban and roll back

To release a single IP from one jail:

sudo fail2ban-client set postfix-sasl unbanip 192.0.2.50

To release an IP from every jail at once:

sudo fail2ban-client unban 192.0.2.50

To disable a jail, set enabled = false in jail.local and reload:

sudo fail2ban-client reload

To back out completely and clear every rule Fail2ban created:

sudo systemctl stop fail2ban

Stopping the service flushes its firewall chains and returns the host to its pre-Fail2ban state. Because default bans aren't persisted across restarts, this is a clean rollback.

Where a custom script would fit

If you have a genuinely non-standard log format or want an action Fail2ban's shipped filters don't cover, the supported path is still to write your own filter (a regex in filter.d/) and your own action, not to script the log parsing from scratch — Fail2ban already handles the timing window, ban expiry, and firewall integration correctly. There's also a recidive jail that bans repeat offenders for longer based on Fail2ban's own log; enable it once the basics are solid. For filter and action syntax, the canonical reference is the Fail2ban documentation and the jail.conf/fail2ban-regex man pages on your system.

Written by
Ketan Aagja

Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.

More about the author →

Was this article helpful?

Tap a star — no sign-in needed.

Be the first to rate this article.