Mailcow’s integrated Watchdog service provides excellent security monitoring, automatically blocking malicious IP addresses and notifying administrators via email. However, in production environments, email alone is insufficient—you need programmatic access to block data for automation, incident response, and centralized monitoring.
This guide explains why Mailcow blocks IP addresses and provides multiple methods to extract this data programmatically.
Why Mailcow Blocks IP Addresses
Fail2ban Integration
Service
Trigger Condition
Ban Duration
SOGo
5 failed logins in 10 min
30 minutes
Postfix
Authentication failures
Configurable
Dovecot
IMAP/POP3 auth failures
Configurable
ACME
Certificate request failures
Variable
Common Indicators:
Brute force login attempts
Invalid credentials
Protocol violations
Rate limit exceeded
Postscreen Protection
Postscreen protects against:
Connection overload
SMTP protocol abuse
Zombie mailers
Indicators:
Too many RCPT TO commands
Invalid SMTP pipelining
Connection rate exceeding thresholds
Rspamd Rate Limiting
Greylisting and reputation-based blocking:
Unknown sender deferral
Message rate limits
Poor reputation scores
Programmatic Access Methods
Method 1: Docker Log Parsing
Access logs directly from containers:
1 2 3 4 5 6 7 8
# Live fail2ban log stream docker-compose logs --tail=100 -f fail2ban
#!/usr/bin/env python3 import subprocess import re import json from datetime import datetime
defget_fail2ban_bans(): result = subprocess.run( ['docker-compose', 'logs', '--since=24h', 'fail2ban'], cwd='/opt/mailcow-dockerized', capture_output=True, text=True ) pattern = r'(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}).*Ban\s+([\d\.]+)' bans = [] for line in result.stdout.split('\n'): match = re.search(pattern, line) ifmatch: timestamp, ip = match.groups() service = "sogo"if"sogo"in line else \ "postfix"if"postfix"in line else \ "dovecot"if"dovecot"in line else"other" bans.append({ 'ip': ip, 'timestamp': timestamp, 'service': service }) return bans
if __name__ == '__main__': print(json.dumps(get_fail2ban_bans(), indent=2))
Method 2: Redis Query
Mailcow stores block data in Redis:
1 2 3 4 5 6 7
# Enter Redis container docker-compose exec redis-mailcow redis-cli
# List blocked IPs KEYS fail2ban:* GET fail2ban:192.168.1.100 TTL fail2ban:192.168.1.100
Python Redis client:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
import redis
defget_redis_blocks(): r = redis.Redis(host='172.22.1.253', port=6379, decode_responses=True) blocks = [] for key in r.scan_iter("fail2ban:*"): ip = key.split(":")[1] blocks.append({ 'ip': ip, 'ttl': r.ttl(key), 'data': r.get(key) }) return blocks
Method 3: Fail2ban Client
Direct fail2ban socket access:
1 2 3 4 5 6 7 8
# List all jails docker-compose exec fail2ban fail2ban-client status
# Specific jail status docker-compose exec fail2ban fail2ban-client status sogo-auth
# View banned IPs docker-compose exec fail2ban fail2ban-client status sogo-auth | grep "Banned IP list"
Method 4: API Integration
For push-based monitoring, use Mailcow’s API:
1 2 3 4
# Requires API key from mailcow UI curl -X GET \ https://mailcow.example.com/api/v1/get/fail2ban \ -H "X-API-Key: YOUR-API-KEY"
defcheck_ip_reputation(ip): """Check if IP is from known good source""" try: # Check against whitelist or reputation service response = requests.get(f"https://api.abuseipdb.com/api/v2/check?ipAddress={ip}", headers={"Key": "YOUR-API-KEY"}, timeout=5) data = response.json() return data['data']['abuseConfidenceScore'] < 25 except: returnFalse
defunblock_if_safe(ip): if check_ip_reputation(ip): subprocess.run([ 'docker-compose', 'exec', '-T', 'fail2ban', 'fail2ban-client', 'unban', ip ], cwd='/opt/mailcow-dockerized') print(f"Unblocked {ip} based on reputation check")