Intrusion Detection -- Fail2ban¶
Fail2ban watches the mail server logs in real time and automatically bans IP addresses that show signs of attack -- brute-force login attempts, spam delivery, rate limit violations, and other abuse patterns. It is the bouncer at the door.
Host-side install, not a container
mailer/intrusion_detection/ is not in any compose file and does not run inside the postfix/dovecot containers. It is installed directly on the host by mailer/intrusion_detection/scripts/install.sh (run as root), which installs the fail2ban package and deploys the jail config, filters, and webhook action from this directory. Banning at the host's iptables is the point -- a container could not block traffic ahead of Docker's own published ports. Whether fail2ban is actually installed on a given host is host state; check systemctl status fail2ban there.
What It Does¶
- Monitors Postfix and Dovecot logs for attack patterns
- Automatically bans offending IPs using iptables firewall rules
- Sends webhook notifications on ban/unban events
- Configurable ban durations, thresholds, and detection windows
- Multiple jails for different attack types
How It Works¶
flowchart LR
Logs["/var/log/mail.log\n/var/log/dovecot.log"] --> F2B["Fail2ban\nDaemon"]
F2B --> Filter1["postfix-auth filter"]
F2B --> Filter2["dovecot-auth filter"]
F2B --> Filter3["postfix-spam filter"]
F2B --> Filter4["rate-limit filter"]
F2B --> Filter5["dovecot-brute filter"]
Filter1 --> Action["Ban Action:\niptables + webhook"]
Filter2 --> Action
Filter3 --> Action
Filter4 --> Action
Filter5 --> Action
Action --> IPTables["iptables\n(block IP)"]
Action --> Webhook["Webhook\nNotification"] Fail2ban works in a simple loop:
- Tail the log files
- Match each line against filter regex patterns
- Count matches per IP within a time window (
findtime) - If count exceeds
maxretry, trigger the ban action - After
bantimeexpires, unban the IP
Jail Configuration¶
All jails are defined in mailer/intrusion_detection/config/fail2ban.conf:
Postfix SMTP Auth Failures¶
[postfix-auth]
enabled = true
port = smtp,465,587
filter = postfix-auth
logpath = /var/log/mail.log
maxretry = 3 # Ban after 3 failed auth attempts
bantime = 3600 # Ban for 1 hour
findtime = 600 # Within a 10-minute window
This catches someone trying to guess SMTP login credentials. Three wrong passwords in 10 minutes and they are locked out for an hour.
Dovecot Auth Failures¶
[dovecot-auth]
enabled = true
port = pop3,pop3s,imap,imaps
filter = dovecot-auth
logpath = /var/log/dovecot.log
maxretry = 3
bantime = 3600
findtime = 600
Same idea but for IMAP/POP3 login attempts.
Postfix Spam Attempts¶
[postfix-spam]
enabled = true
port = smtp,465,587
filter = postfix-spam
logpath = /var/log/mail.log
maxretry = 10 # More lenient -- some rejections are normal
bantime = 7200 # Ban for 2 hours
findtime = 3600 # Within a 1-hour window
This catches IPs that repeatedly try to send mail that gets rejected (bad recipients, bad senders, RBL hits).
Rate Limit Violations¶
[rate-limit]
enabled = true
port = smtp,465,587
filter = rate-limit
logpath = /var/log/mail.log
maxretry = 5
bantime = 1800 # Ban for 30 minutes
findtime = 300 # Within a 5-minute window
Catches IPs that keep hitting rate limits, suggesting automated abuse.
Dovecot Brute Force¶
[dovecot-brute]
enabled = true
port = pop3,pop3s,imap,imaps
filter = dovecot-brute
logpath = /var/log/dovecot.log
maxretry = 5
bantime = 7200 # Ban for 2 hours
findtime = 1800 # Within a 30-minute window
A stricter jail that catches persistent but slower brute-force attempts that might slip under the dovecot-auth jail's radar.
Custom Filters¶
Filter files live in mailer/intrusion_detection/filters/ and contain regex patterns that match log lines:
postfix-auth.conf¶
[Definition]
failregex = warning: .*\[<HOST>\]: SASL (?:LOGIN|PLAIN|DIGEST-MD5|CRAM-MD5) authentication failed
dovecot-auth.conf¶
[Definition]
failregex = auth-worker.*: sql\(.*,<HOST>\): Password mismatch
auth: Login failed.*rip=<HOST>
postfix-spam.conf¶
[Definition]
failregex = NOQUEUE: reject: RCPT from .*\[<HOST>\]
warning: non-SMTP command from .*\[<HOST>\]
rate-limit.conf¶
The <HOST> placeholder is special to Fail2ban -- it extracts the IP address from the matched line.
Ban Actions¶
Each jail uses two actions:
iptables-multiport¶
Blocks the IP at the firewall level:
# What happens when an IP is banned
iptables -I f2b-postfix-auth -s 1.2.3.4 -j REJECT --reject-with icmp-port-unreachable
Webhook Notification¶
Sends a webhook notification on ban/unban events via mailer/intrusion_detection/scripts/fail2ban-webhook.py:
{
"event": "fail2ban.ban",
"jail": "postfix-auth",
"ip": "1.2.3.4",
"timestamp": "2025-01-15T10:30:00Z",
"ban_duration": 3600,
"failures": 3
}
Whitelisting¶
To prevent banning trusted IPs (your office, monitoring services, etc.), add them to the ignoreip directive:
Docker internal networks are whitelisted by default since inter-container communication should never be banned.
Monitoring Banned IPs¶
View current bans¶
# List all banned IPs across all jails
docker exec <container> fail2ban-client status
# List banned IPs for a specific jail
docker exec <container> fail2ban-client status postfix-auth
Manually unban an IP¶
Check ban history¶
The fail2ban-manager.py script provides an API for querying ban history and managing bans programmatically. The monitoring service queries this for dashboard reporting.
Configuration¶
Defaults come from the [DEFAULT] section of mailer/intrusion_detection/config/fail2ban.conf (literal values, not environment variables):
| Setting | Default | Description |
|---|---|---|
bantime | 1800 | Default ban time in seconds (per-jail values override) |
findtime | 600 | Default detection window in seconds |
maxretry | 5 | Default max failures before ban |
destemail / sender | admin@yourdomain.com / fail2ban@yourdomain.com | Ban notification mail (edit before installing) |
The webhook action (actions/webhook.conf + scripts/fail2ban-webhook.py) is attached to every jail alongside iptables-multiport.
Gotchas¶
Docker Networking
In a Docker environment, Fail2ban sees the Docker bridge IP, not the client's real IP, unless you use host networking or configure Postfix to log the X-Forwarded-For header. Make sure your setup preserves client IPs.
Log Paths
The jail config's logpath values (/var/log/mail.log, /var/log/dovecot.log) are the classic host locations. In this stack, Postfix and Dovecot logs are bind-mounted to ./logs/mailer/postfix/mail.log and ./logs/mailer/dovecot/ under the project root -- point the jails at those host paths (or symlink) when installing, or fail2ban tails files nothing writes.