# Configuring Fail2Ban for a Podman Mail Server With UFW Bans


My self-hosted mail server, running Postfix and Dovecot, lives in a rootless Podman container on a netcup VPS. A Postfix log summary showed 2,477 rejected messages in one day, with 2,471 coming from the same IP. There were also 152 failed SASL LOGIN attempts, along with scanning, relay abuse, and TLS probes.

Postfix can reject spam itself, but SASL password guessing is a separate problem. Fail2Ban identifies sources with repeated failures in the logs and tells the firewall to ban them. Since the server already uses UFW to manage inbound rules, Fail2Ban uses its built-in `ufw` action instead of writing directly to iptables.

## Environment

| Item                | Value                                                |
| ------------------- | ---------------------------------------------------- |
| Host OS             | Arch Linux                                           |
| Container runtime   | Rootless Podman                                      |
| Mail server image   | `ghcr.io/docker-mailserver/docker-mailserver:latest` |
| Mail server service | `systemd-mailserver`                                 |
| Quadlet file        | `~/pod/docker-mailserver/mailserver.container`       |
| Host mail log       | `~/pod/docker-mailserver/logs/mail.log`              |
| Container mail log  | `/var/log/mail/mail.log`                             |
| Fail2Ban            | 1.1.0                                                |
| Firewall            | UFW                                                  |

## 1. Install and inspect the current state

Install and start Fail2Ban:

```bash
sudo pacman -S fail2ban
sudo systemctl enable --now fail2ban
```

Confirm that UFW is enabled, Fail2Ban is running, and see which jails are active:

```bash
sudo ufw status verbose
sudo systemctl is-active fail2ban
sudo fail2ban-client status
```

UFW should report `Status: active`. Once this configuration is complete, the jail list includes:

```text
Status
|- Number of jail:  3
`- Jail list:       postfix, postfix-sasl, sshd
```

`backend` and `banaction` are separate settings:

- `backend` determines where Fail2Ban reads failure records, such as journald or a regular log file.
- `banaction` determines how an IP is banned once it reaches the threshold.

The mail container writes logs to a host-mounted file, so the mail jails need `backend = polling`. Switching to UFW does not change log collection.

## 2. Make the Postfix jails read the container log

A traditional Postfix installation usually writes logs to `/var/log/mail.log`. This path inside the container is not visible to Fail2Ban on the host, so use the actual host-side volume path instead.

Fail2Ban on Arch selects the systemd log backend for Postfix through `paths-arch.conf` by default. The logs here are files, so explicitly configure `polling` in a local override.

Create or edit `/etc/fail2ban/jail.d/mailserver.local`:

```ini
[DEFAULT]
postfix_backend = polling
postfix_log = /home/nite/pod/docker-mailserver/logs/mail.log

[postfix-sasl]
enabled   = true
mode      = auth
port      = smtp,465,submission,imap,imaps,pop3,pop3s
logpath   = /home/nite/pod/docker-mailserver/logs/mail.log
backend   = polling
maxretry  = 5
findtime  = 10m
bantime   = 1h
banaction = ufw

[postfix]
enabled   = true
mode      = more
port      = smtp,465,submission
logpath   = /home/nite/pod/docker-mailserver/logs/mail.log
backend   = polling
maxretry  = 5
findtime  = 10m
bantime   = 1h
banaction = ufw
```

Use the `.local` suffix. Fail2Ban reads `jail.conf`, `jail.d/*.conf`, `jail.local`, and `jail.d/*.local` in order, so this local file overrides the defaults last.

## 3. Change the sshd banning action to UFW

Do not edit `/etc/fail2ban/jail.conf`; it is package-provided and may be overwritten during an update.

To make UFW the default action for jails, edit `/etc/fail2ban/jail.local` and set this in `[DEFAULT]`:

```ini
[DEFAULT]
banaction = ufw
banaction_allports = ufw
```

To switch only the SSH jail, use this instead:

```ini
[sshd]
banaction = ufw
```

Postfix and postfix-sasl already set `banaction = ufw` individually in `mailserver.local`, so they do not depend on the global setting.

Fail2Ban's built-in UFW action calls `ufw` to add and remove rules. There is no longer a need to inspect Fail2Ban ban chains with `iptables -L`; check UFW directly instead.

## 4. Validate the configuration, then restart the jails

Parse the configuration after editing:

```bash
sudo fail2ban-client -d >/dev/null
```

This command only checks the parsed result; it does not modify the firewall.

After changing an action, restart the service completely:

```bash
sudo systemctl restart fail2ban
```

## 5. Verify logs, actions, and UFW rules

Confirm that the mail jails are reading the host log file:

```bash
sudo fail2ban-client status postfix-sasl
sudo fail2ban-client status postfix
```

The `File list` output should be:

```text
/home/nite/pod/docker-mailserver/logs/mail.log
```

To test whether the SASL filter matches existing log entries without issuing a ban:

```bash
sudo fail2ban-regex /home/nite/pod/docker-mailserver/logs/mail.log "postfix[mode=auth]"
```

After restarting the service, inspect the runtime actions instead of relying only on the configuration files:

```bash
sudo systemctl is-active fail2ban
sudo fail2ban-client get sshd actions
sudo fail2ban-client get postfix actions
sudo fail2ban-client get postfix-sasl actions
```

The actual output in this case was:

```text
active
The jail sshd has the following actions:
ufw
The jail postfix has the following actions:
ufw
The jail postfix-sasl has the following actions:
ufw
```

## 6. Run an end-to-end test

```bash
sudo fail2ban-client set sshd banip 192.0.2.1
sudo ufw status numbered | grep -F 192.0.2.1
sudo fail2ban-client set sshd unbanip 192.0.2.1
```

The second command should show a UFW rule with a `by Fail2Ban` comment. The third command removes the test ban immediately.

## References

- [Fail2Ban 1.1.0 UFW action](https://github.com/fail2ban/fail2ban/blob/1.1.0/config/action.d/ufw.conf)
- [Fail2Ban 1.1.0 default jail configuration](https://github.com/fail2ban/fail2ban/blob/1.1.0/config/jail.conf)


---

> Author: Nite  
> URL: https://www.nite07.com/en/posts/fail2ban-postfix-podman/  

