Suggestion: Postfix anti-flood rate limiting and Fail2ban log backend issues on Ubuntu 24.04

SYSTEM INFORMATION
OS type and version Ubuntu Linux 24.04.4
Webmin version 2.653
Virtualmin version 8.1.0 GPL
Webserver version ?
Related packages PHP 8.5

Hi Virtualmin team and community,

I’m writing to share an issue I encountered and suggest improvements that could benefit all Virtualmin users, especially those running Ubuntu 24.04.

The problem

My Virtualmin server (Ubuntu 24.04, 16 GB RAM, 4 GB swap) went down multiple times due to SMTP brute-force attacks that overwhelmed Postfix.

The attacks generated thousands of SASL authentication failures per hour, consuming RAM and eventually triggering the OOM killer, which terminated PHP-FPM and made the entire server unresponsive.

What I found

After investigating, I discovered two issues that I believe are worth addressing:

  1. Postfix had no rate limiting configured
    By default, my Postfix installation (managed by Virtualmin) had all rate limiting parameters set to 0 (disabled):
smtpd_client_connection_rate_limit = 0
smtpd_client_message_rate_limit = 0
smtpd_client_recipient_rate_limit = 0
smtpd_client_auth_rate_limit = 0

This meant a single IP could open unlimited connections and attempt unlimited authentication failures without any throttling. During a brute-force attack, this quickly overwhelmed the server.
I had to manually add these settings to /etc/postfix/main.cf:

smtpd_client_connection_rate_limit = 10
smtpd_client_message_rate_limit = 30
smtpd_client_recipient_rate_limit = 50
smtpd_client_auth_rate_limit = 5
anvil_rate_time_unit = 60s

This is a recent edit I done so is still under testing but I believe should not cause issues.

2. Fail2ban was not reading Postfix logs correctly

Fail2ban was installed and running, but it was configured with backend = systemd (the default on Ubuntu 24.04). This backend reads from the systemd journal, but Postfix on Ubuntu 24.04 logs to /var/log/mail.log, not to the journal.

As a result, Fail2ban showed as “active” but was effectively blind to the SMTP attacks:

# Before the fix:
sudo fail2ban-client get postfix logpath
No file is currently monitored

I had to manually configure /etc/fail2ban/jail.local to use backend = polling and specify the correct log paths:

[DEFAULT]
backend = polling

[postfix]
enabled = true
mode = aggressive
logpath = /var/log/mail.log

[postfix-sasl]
enabled = true
backend = polling
logpath = /var/log/mail.log

[dovecot]
enabled = true
logpath = /var/log/mail.log

[sshd]
enabled = true
logpath = /var/log/auth.log

After the fix, Fail2ban immediately started banning dozens of attacking IPs:

# After the fix:
sudo fail2ban-client get postfix logpath
Current monitored log file(s):
`- /var/log/mail.log

Status for the jail: postfix
|- Actions
|  |- Currently banned: 49
|  `- Total banned: 49

Suggestions for Virtualmin
I believe these issues could be addressed in Virtualmin to improve security out of the box:

  1. Consider adding Postfix rate limiting by default when Virtualmin configures a mail server. Even conservative values like the ones above would provide a first line of defense against brute-force attacks without impacting legitimate mail flow.
  2. Detect and fix Fail2ban log backend issues during installation. On Ubuntu 24.04, Virtualmin could check whether Postfix logs to the journal or to /var/log/mail.log, and configure Fail2ban accordingly. Alternatively, display a warning in the Virtualmin dashboard if Fail2ban is not monitoring Postfix logs.

Additional notes

I also protected my PHP cron jobs with flock and enabled PHP-FPM auto-restart via systemd, but these are general best practices not specific to Virtualmin.

The main takeaway is that Postfix without rate limiting + Fail2ban not reading the correct logs = server vulnerable to brute-force attacks. I hope this feedback helps improve Virtualmin for everyone.

Happy to provide more details or test any proposed changes.
Cheers!

I have the same OS and postfix does log to the journal, to test this run the following in a terminal

journalctl --no-hostname --lines 100 -u postfix@-.service

you should the output from postfix from the journal. As I don’t use fail2ban any more (not for a couple of years) I seem to remember some issue with that version of fail2ban not being able to read the journal correctly (fail2ban may now have changed)
So therefore

is not 100% correct

Hi @jimr1 and the Virtualmin team,
Thank you for the clarification and the command! I stand corrected. I ran the exact command you suggested:

journalctl --no-hostname --lines 100 -u postfix@-.service

You are absolutely right: Postfix is logging to the systemd journal on Ubuntu 24.04. Here is a snippet of what I saw immediately showing active brute-force attacks.

So my initial assumption that Postfix had stopped writing to the journal was wrong. The real issue, as you pointed out, is that Fail2ban (which defaults to backend = systemd on Ubuntu 24.04) seems to have trouble parsing the journal entries correctly, leaving the server blind to the attacks.

The Solution that worked:
I proceeded with the workaround to force Fail2ban to read the traditional text log. Since rsyslog is still actively writing to /var/log/mail.log alongside the journal, I added backend = polling to my /etc/fail2ban/jail.local:

[DEFAULT]
backend = polling

[postfix-sasl]
enabled = true
backend = polling
logpath = /var/log/mail.log

The results were immediate. Fail2ban started catching the active brute-force attempts and banning IPs right away:

root@server:~# sudo fail2ban-client status postfix-sasl
Status for the jail: postfix-sasl
|- Filter
|  |- Currently failed: 35
|  |- Total failed:     166
|  `- File list:        /var/log/mail.log
`- Actions
   |- Currently banned: 4
   |- Total banned:     4

Takeaway for Virtualmin on Ubuntu 24.04:

While the systemd journal is working perfectly for Postfix, the default backend = systemd configuration in Fail2ban still seems unreliable for parsing these specific Postfix/SASL logs on this OS release. Until Fail2ban’s systemd filters are fully optimized, forcing backend = polling to read /var/log/mail.log is a solid and reliable fallback.

I am also implementing the Postfix rate-limiting parameters in main.cf (as mentioned in my first post) as an extra layer of defense to prevent RAM exhaustion before Fail2ban can trigger the ban.

Thanks again for the help and for pointing me in the right direction regarding the journal!

I would also add postscreen, it does a reasonable job of stopping known bad actors that have not been caught by fail2ban (fail2ban requires a log entry) postscreen checks the ip address against configurable spam block lists e.g spamhous and spamcop. Before any logs are really written a part from it’s own entries