Mail Server Protection: CrowdSec Intrusion Prevention System
Part 3 of the Building a Modern Mail Server on Debian 13 series
Introduction
CrowdSec is a modern, collaborative intrusion prevention system (IPS) that protects your mail server from brute-force attacks, spam, and abuse. Unlike traditional tools like Fail2ban, CrowdSec:
- Shares threat intelligence – Benefits from global community blocklists
- Detects sophisticated attacks – Catches slow, distributed attacks
- Supports IPv6 natively – Full IPv4 and IPv6 protection
- Flexible architecture – Separates detection from blocking
This guide provides battle-tested configurations based on real-world deployment and attack patterns.
What This Guide Covers
- Complete CrowdSec installation and configuration
- Dovecot 2.4 support (Debian 13 compatibility)
- Protection for IMAP/POP3, SMTP AUTH, and PostfixAdmin
- Properly-tuned detection for both fast and slow attacks
- IPv4 and IPv6 protection
- Real-world testing and verification
Prerequisites
Before starting:
- Debian 13 system with Postfix + Dovecot installed (Part 1)
- PostfixAdmin configured (Part 2)
- Logs configured in journald (systemd journal)
- Basic understanding of mail server logs
System Requirements:
- 512MB RAM minimum
- nftables (default on Debian 13)
- Direct network access (if behind proxy, see Architecture Considerations)
Understanding CrowdSec Components
Core Components
┌────────────────────────────────┐
│ Mail Server │
│ (Your Server) │
└───────────────┬────────────────┘
│
▼
┌────────────────────────────────┐
│ Logs │
│ ← Postfix, Dovecot, Apache │
└───────────────┬────────────────┘
│
▼
┌────────────────────────────────┐
│ CrowdSec │
│ Agent │
│ ← Detection Engine │
│ (Parsers + Scenarios) │
└───────────────┬────────────────┘
│
▼
┌────────────────────────────────┐
│ Local API │
│ ← Decision Database │
└───────────────┬────────────────┘
│
▼
┌────────────────────────────────┐
│ Bouncers │
│ (nftables) │
│ ← Enforcement │
│ (Firewall Rules) │
└────────────────────────────────┘Terminology
Terminology
- Parser: Extracts data from logs (IPs, usernames, etc.)
- Scenario: Defines attack patterns and triggers bans
- Decision: Ban verdict for an IP address
- Bouncer: Enforces decisions (blocks IPs in firewall)
- Collection: Bundle of related parsers and scenarios
Installation
Step 1: Add CrowdSec Official Repository
Why this is important: Debian’s default repositories may contain outdated versions. The official CrowdSec repository ensures that you receive the latest stable version, complete with all features and security updates.
# Install prerequisites
apt install -y curl gnupg
# Create keyrings directory (if it doesn't exist)
mkdir -p /etc/apt/keyrings
# Add CrowdSec GPG key (Debian best practice location)
curl -fsSL https://packagecloud.io/crowdsec/crowdsec/gpgkey | gpg --dearmor -o /etc/apt/keyrings/crowdsec.gpg
# Add CrowdSec repository
cat > /etc/apt/sources.list.d/crowdsec.list << 'EOF'
deb [signed-by=/etc/apt/keyrings/crowdsec.gpg] https://packagecloud.io/crowdsec/crowdsec/any any main
deb-src [signed-by=/etc/apt/keyrings/crowdsec.gpg] https://packagecloud.io/crowdsec/crowdsec/any any main
EOF
# Update package list
apt updateNote: CrowdSec uses distribution-agnostic repositories (any any), not OS-specific paths. This ensures compatibility across all Debian-based systems.
Verify repository added:
apt-cache policy crowdsecExpected output:
crowdsec:
Installed: (none)
Candidate: 1.7.x
Version table:
1.7.x 500
500 https://packagecloud.io/crowdsec/crowdsec/any any/main ...Step 2: Install CrowdSec
# Install CrowdSec and firewall bouncer
apt install -y crowdsec crowdsec-firewall-bouncer
# Verify installation
systemctl status crowdsec
cscli versionExpected output:
CrowdSec version: v1.7.x or laterStep 3: Verify Firewall Bouncer
# Check bouncer is running
systemctl status crowdsec-firewall-bouncer
# Verify CrowdSec nftables table exists
nft list tables | grep crowdsecExpected output:
table ip crowdsec
table ip6 crowdsec6Step 4: Basic Configuration
CrowdSec is now installed with default settings. Next, we’ll configure it for mail server protection.
Official Collections (What’s Actually Included)
IMPORTANT: Official collections have limited mail server protection. Understanding what’s included helps you know what custom scenarios you need.
Step 1: Install Official Collections
# Install Postfix collection
cscli collections install crowdsecurity/postfix
# Install Dovecot collection
cscli collections install crowdsecurity/dovecot
# Reload CrowdSec
systemctl reload crowdsecStep 2: Understand What You Actually Get
Postfix Collection Reality
# Inspect what's actually in the collection
cscli collections inspect crowdsecurity/postfixWhat’s ACTUALLY included:
- ✅
crowdsecurity/postfix-spam– Detects spam attempts - ✅
crowdsecurity/postfix-relay-denied– Detects relay abuse - ✅
crowdsecurity/postfix-helo-rejected– Detects HELO/EHLO violations - ✅
crowdsecurity/postfix-non-smtp-command– Detects protocol violations
What’s MISSING (not included!):
- ❌ No SMTP AUTH brute-force protection!
- ❌ No protection against authentication attempts
- ❌ No slow attack detection
Dovecot Collection Reality
# Inspect Dovecot collection
cscli collections inspect crowdsecurity/dovecotWhat’s ACTUALLY included:
- ✅
crowdsecurity/dovecot-spam– Detects spam attempts
What’s MISSING:
- ❌ No IMAP/POP3 brute-force protection!
- ❌ No authentication failure detection
- ❌ No user enumeration protection
Step 3: Verify What’s Running
# List loaded collections
cscli collections list | grep -E "postfix|dovecot"
# List actual running scenarios
cscli scenarios list | grep -E "postfix|dovecot"Critical Note: Official collections DO NOT protect against authentication brute-force attacks! Custom scenarios are essential.
Custom Dovecot 2.4 Parser (Essential for Debian 13)
Why This Is Needed: Debian 13 includes Dovecot 2.4, which uses a different log format than older versions. The default CrowdSec parser cannot parse Dovecot 2.4 logs, meaning authentication failures are silently discarded.
Understanding the Problem
Dovecot 2.4 log format (Debian 13):
auth-worker(account,45.144.212.19)<189477>: request [1]: sql: unknown user
auth-worker(account,45.144.212.19)<189478>: Password mismatchOld Dovecot format (what default parser expects):
dovecot: auth: unknown user account from 45.144.212.19
dovecot: auth: Password mismatch for user account from 45.144.212.19Verification Test (Proves Default Parser Fails)
Important: When testing with cscli explain, you must provide a complete syslog-formatted line including timestamp and hostname (e.g., Dec 06 10:00:00 mail dovecot: message). Just the message portion won’t parse correctly.
# Test with real Dovecot 2.4 log line (complete syslog format)
echo 'Dec 06 10:00:00 mail dovecot: auth-worker(account,45.144.212.19)<189477>: request [1]: sql: unknown user' > /tmp/test.log
cscli explain --file /tmp/test.log --type syslog --verbose
rm /tmp/test.logResult with default parser:
Event leaving node: ko
Log didn't finish stage s01-parse
Discarding lineThis proves the event is DISCARDED – no protection!
Step 1: Create Custom Dovecot 2.4 Parser
IMPORTANT: Custom parsers must go in /etc/crowdsec/parsers/s01-parse/ (NOT s00-user!). See “Directory Structure Explained” section for why.
# Create parser directory
mkdir -p /etc/crowdsec/parsers/s01-parse
# Create custom Dovecot 2.4 parser
cat > /etc/crowdsec/parsers/s01-parse/dovecot-2.4.yaml << 'EOF'
onsuccess: next_stage
name: my/dovecot-2.4
description: "Parse Dovecot 2.4 authentication logs (Debian 13 format)"
filter: "evt.Parsed.program == 'dovecot'"
grok:
name: DOVECOT24
apply_on: message
pattern: |
auth-worker\(%{DATA:account},%{IP:source_ip}\)<%{NUMBER}>\: (?:request \[\d+\]: sql: )?%{GREEDYDATA:action}
statics:
- meta: log_type
value: dovecot_auth
- meta: service
value: imap
- meta: source_ip
expression: evt.Parsed.source_ip
nodes:
- grok:
name: DOVECOT24_FAIL
apply_on: action
statics:
- meta: auth_status
value: failed
pattern: "(?:unknown user|Password mismatch)"
EOFStep 2: Reload and Verify
# Reload CrowdSec
systemctl reload crowdsec
sleep 3
# Verify parser loaded
cscli parsers list | grep "my/dovecot-2.4"Expected:
my/dovecot-2.4 🏠 enabled,local /etc/crowdsec/parsers/s01-parse/dovecot-2.4.yamlStep 3: Test Parser Works
# Test with real Dovecot 2.4 log (complete syslog format)
echo 'Dec 06 10:00:00 mail dovecot: auth-worker(testuser,45.144.212.19)<189477>: request [1]: sql: unknown user' > /tmp/test.log
cscli explain --file /tmp/test.log --type syslog --verbose
rm /tmp/test.logExpected success output:
+ Grok "DOVECOT24" returned data
{"account":"testuser","source_ip":"45.144.212.19"...}
Event leaving node: ok
Event reaching next stageStep 4: Verify in Production
# Wait for real authentication attempts, then check metrics
cscli metricsLook for:
Acquisition Metrics:
| journalctl:dovecot.service | X | X | - | X | - |
↑ ↑ ↑
Lines Lines Lines
read parsed poured✅ Success = Lines parsed > 0 and Lines unparsed = 0 or very low
Directory Structure Explained
Why /etc/crowdsec/parsers/s01-parse/ and NOT /etc/crowdsec/parsers/s00-user/?
CrowdSec processes logs in stages:
s00-raw (syslog parsing) → Success
↓
s01-parse (ALL parsers try in parallel)
├─ Official dovecot-logs parser → FAILS on Dovecot 2.4
├─ Custom dovecot-2.4 parser (if in s01-parse) → SUCCESS ✅
└─ At least ONE success → Continue to s02-enrich
s00-user (only runs if s01-parse succeeded)
└─ Parser here → TOO LATE! Event already discarded if s01-parse failedKey principle: Directory name determines execution stage, NOT the stage: field in YAML!
If the parser in /etc/crowdsec/parsers/s00-user/:
- Official parser fails in s01-parse
- Event discarded immediately
- s00-user stage never reached
- Custom parser loads but never executes (0 hits in metrics)
If the parser in /etc/crowdsec/parsers/s01-parse/:
- Runs in parallel with official parser
- Catches what official parser misses
- Event continues to scenarios
Custom Scenarios for Mail Protection
Official collections lack brute-force protection. We’ll create custom scenarios using properly-tuned leaky buckets based on the official ssh-slow-bf pattern.
Understanding Leaky Buckets
Why leaky buckets (not counter)?
- ✅ CrowdSec official standard (all scenarios use leaky)
- ✅ Battle-tested and well-documented
- ✅ Counter buckets have syntax issues and no examples
Leaky bucket principle:
capacity: 3 # Bucket holds 3 events max
leakspeed: "8h" # Empties 3 events over 8 hours
# = Leaks 1 event every 2.67 hours
Attack pattern:
Hour 0:00 → Attack #1 → Bucket: 1.0
Hour 1:30 → Attack #2 → Bucket: 1.4 (leaked 0.6)
Hour 3:00 → Attack #3 → Bucket: 2.3 (leaked 0.5)
Hour 4:30 → Attack #4 → Bucket: 3.0+ → BAN! ✅Key insight: With leakspeed: 8h, attacks spaced up to ~2.5 hours apart still accumulate!
Step 1: Create Scenarios Directory
# Create directory for custom scenarios
mkdir -p /etc/crowdsec/scenarios/s00-user
Note: Scenarios typically go in s00-user (unlike parsers, which need s01-parse).
Step 2: Create Dovecot Brute-Force Protection
cat > /etc/crowdsec/scenarios/s00-user/dovecot-bf.yaml << 'EOF'
type: leaky
name: my/dovecot-bf
description: "Detect IMAP/POP3 authentication brute force (slow and fast)"
filter: "evt.Parsed.program == 'dovecot' && (evt.Parsed.message contains 'unknown user' || evt.Parsed.message contains 'Password mismatch')"
leakspeed: "8h"
capacity: 3
groupby: evt.Meta.source_ip
blackhole: 4h
labels:
service: imap
type: bruteforce
remediation: true
behavior: "imap:bruteforce"
label: "Dovecot Bruteforce"
EOFConfiguration explained:
capacity: 3– Trigger ban after 3 authentication failuresleakspeed: 8h– Bucket empties over 8 hours (catches slow attacks)blackhole: 4h– Ban duration: 4 hoursgroupby: source_ip– Track per IP address
Attack coverage:
- Fast attacks: 3 attempts in minutes → Banned
- Slow attacks: 3 attempts over several hours → Banned
- Catches attacks spaced up to ~2.5 hours apart
Step 3: Create Postfix SMTP AUTH Protection
cat > /etc/crowdsec/scenarios/s00-user/postfix-bf.yaml << 'EOF'
type: leaky
name: my/postfix-bf
description: "Detect SMTP AUTH brute force (slow and fast)"
filter: "evt.Parsed.program startsWith 'postfix/' && evt.Parsed.message contains 'SASL' && evt.Parsed.message contains 'authentication failed'"
leakspeed: "8h"
capacity: 3
groupby: evt.Meta.source_ip
blackhole: 4h
labels:
service: smtp
type: bruteforce
remediation: true
behavior: "smtp:bruteforce"
label: "Postfix SMTP AUTH Bruteforce"
EOFWhat this catches:
Example Postfix log:
Dec 05 17:05:07 mail postfix/smtpd[275212]: warning: unknown[213.209.157.87]:
SASL LOGIN authentication failed: (reason unavailable), sasl_username=spamAttack pattern observed in the wild:
- 1 attempt every 90-120 minutes
- Testing usernames: spam, admin, test, oracle, postmaster, etc.
- Official scenarios MISS these (no brute-force protection!)
- Our scenario CATCHES them ✅
Step 4: Create PostfixAdmin Web Protection
cat > /etc/crowdsec/scenarios/s00-user/postfixadmin-bf.yaml << 'EOF'
type: leaky
name: my/postfixadmin-bf
description: "Detect PostfixAdmin login brute force"
filter: "evt.Meta.log_type == 'http_access-log' && evt.Meta.http_verb == 'POST' && evt.Meta.http_path startsWith '/login.php'"
leakspeed: "2h"
capacity: 5
groupby: evt.Meta.source_ip
blackhole: 2h
labels:
service: http
type: bruteforce
remediation: true
behavior: "http:bruteforce"
label: "PostfixAdmin Bruteforce"
EOFWhy different settings?
capacity: 5(vs 3 for mail) – Web users make more typosleakspeed: 2h(vs 8h for mail) – Web attacks usually fasterblackhole: 2h(vs 4h for mail) – Shorter ban for web interface- Reduces false positives from legitimate users
Step 5: Configure Apache Log Acquisition
For PostfixAdmin protection to work, CrowdSec needs to read Apache logs:
# Create Apache acquisition config
cat > /etc/crowdsec/acquis.d/apache2.yaml << 'EOF'
---
filenames:
- /var/log/apache2/postfixadmin_access.log
- /var/log/apache2/error.log
labels:
type: apache2
---
EOFStep 6: Reload and Verify
# Reload CrowdSec to load new scenarios
systemctl reload crowdsec
sleep 3
# Verify all scenarios loaded
cscli scenarios list | grep "my/"Expected output:
my/dovecot-bf 🏠 enabled,local /etc/crowdsec/scenarios/s00-user/dovecot-bf.yaml
my/postfix-bf 🏠 enabled,local /etc/crowdsec/scenarios/s00-user/postfix-bf.yaml
my/postfixadmin-bf 🏠 enabled,local /etc/crowdsec/scenarios/s00-user/postfixadmin-bf.yamlWhy These Settings Work
Real-world test results:
Attack pattern observed:
Time IP Action
16:01 213.209.157.87 AUTH FAIL (username: server)
17:05 213.209.157.87 AUTH FAIL (username: spam) ← 64 min later
18:08 213.209.157.87 AUTH FAIL (username: ftpuser) ← 63 min later
19:12 213.209.157.87 AUTH FAIL (username: oracle) ← 64 min later
19:12 213.209.157.87 🚫 BANNED (4 hours)Leaky bucket calculation:
Attack #1 (16:01): bucket = 1.0
Attack #2 (17:05): leaked 0.4 → bucket = 1.6
Attack #3 (18:08): leaked 0.4 → bucket = 2.2
Attack #4 (19:12): leaked 0.4 → bucket = 2.8
Attack #5 (next): → bucket = 3.0+ → BAN! ✅With leakspeed: 8h, the leak rate (0.375 events/hour) is slower than attack rate, so bucket accumulates!
Architecture Considerations
Direct Access vs Reverse Proxy
CRITICAL: CrowdSec firewall bouncer ONLY works with direct network access!
✅ Working Architecture (Recommended):
Internet → Mail Server (Direct Connection)
├─ Port 25 (SMTP)
├─ Port 993 (IMAPS)
├─ Port 995 (POP3S)
└─ Port 25443 (PostfixAdmin HTTPS)
CrowdSec detects → nftables blocks → ✅ Works!❌ Broken Architecture:
Internet → Nginx Proxy → Mail Server
Mail Server sees proxy IP, not attacker IP
CrowdSec detects attacker but can't block (traffic already inside)
Firewall bouncer ineffective → ❌ Doesn't work!If Behind Reverse Proxy
You have two options:
Option 1: Install CrowdSec on Proxy Server (Best)
- Run CrowdSec + firewall bouncer on proxy
- Configure it to read proxy logs
- Blocks attackers before they reach mail server
Option 2: Use Nginx Bouncer
- Install
crowdsec-nginx-bounceron proxy - Configure with the mail server’s CrowdSec API
- Blocks at the application layer
Option 3: Direct Access (What we use)
- Use a non-standard port (e.g., 25443) for PostfixAdmin
- Bypass proxy for mail services
- Simplest and most effective for mail servers
Container/LXC Considerations
If running in Incus/LXC container:
- ✅ Works great with direct networking
- ✅ nftables works normally in unprivileged containers
- ✅ IPv6 fully supported (both IPv4 and IPv6 addresses banned)
- ⚠️ If using NAT/bridge, ensure original IPs are preserved
- ⚠️ If behind proxy, see “Behind Reverse Proxy” above
Testing and Verification
Step 1: Verify Log Acquisition
# Check CrowdSec is reading logs
cscli metricsLook for the Acquisition Metrics section:
Acquisition Metrics:
| Source | Lines read | Lines parsed | Lines unparsed |
|------------------------------------------------|------------|--------------|----------------|
| journalctl:dovecot.service | 62 | 60 | 2 |
| journalctl:postfix.service | 298 | 96 | 202 |
| file:/var/log/apache2/postfixadmin_access.log | 59 | 59 | - |✅ Good: Lines parsed > 0, unparsed is low
❌ Bad: Lines parsed = 0, all unparsed (parser not working)
Step 2: Verify Parser Success Rate
# Check parser metrics
cscli metrics | grep -A10 "Parser Metrics"Look for:
Parser Metrics:
| Parser | Hits | Parsed | Unparsed |
|---------------------------|------|--------|----------|
| child-my/dovecot-2.4 | 83 | 51 | 32 |
| crowdsecurity/dovecot-logs| 11 | 9 | 2 |✅ Dovecot 2.4 parser working – Shows hits and parsed events
✅ Parse rate ~60-85% is normal (not all logs are auth failures)
Step 3: Test PostfixAdmin Protection (IPv4 and IPv6)
From another server (NOT the mail server):
#!/bin/bash
# Save as test-postfixadmin.sh
# Tests both IPv4 and IPv6 brute-force detection
MAIL_SERVER="https://mail.yourdomain.com:25443"
LOGIN_URL="${MAIL_SERVER}/login.php"
echo "========================================="
echo "Testing PostfixAdmin Brute Force Protection"
echo "========================================="
echo ""
# Test IPv4
echo "Testing IPv4 attacks..."
for i in {1..6}; do
echo " IPv4 Attempt $i at $(date +%H:%M:%S)"
curl -4 -X POST "${LOGIN_URL}" \
-d "fUsername=test@test.com" \
-d "fPassword=wrong${i}" \
-k -s -o /dev/null -w " HTTP: %{http_code}\n"
sleep 2
done
echo ""
echo "Waiting 5 seconds before IPv6 test..."
sleep 5
echo ""
# Test IPv6 (if available)
echo "Testing IPv6 attacks..."
if curl -6 -s -m 2 "${MAIL_SERVER}" -k -o /dev/null 2>/dev/null; then
for i in {1..6}; do
echo " IPv6 Attempt $i at $(date +%H:%M:%S)"
curl -6 -X POST "${LOGIN_URL}" \
-d "fUsername=test@test.com" \
-d "fPassword=ipv6wrong${i}" \
-k -s -o /dev/null -w " HTTP: %{http_code}\n"
sleep 2
done
else
echo " IPv6 not available on this test machine (skipping IPv6 test)"
fi
echo ""
echo "========================================="
echo "Tests complete!"
echo "========================================="
echo ""
echo "On mail server, check bans:"
echo " cscli decisions list"
echo ""
echo "You should see BOTH IPv4 and IPv6 addresses banned!"Run the test:
chmod +x test-postfixadmin.sh
./test-postfixadmin.shOn the mail server, monitor:
# Watch for bans (should see both IPv4 and IPv6)
watch -n 2 'cscli decisions list'
# Check Apache logs
tail -f /var/log/apache2/postfixadmin_access.logExpected result after 5-6 attempts:
Alert ID XX: Ip:YOUR_TEST_IP | my/postfixadmin-bf | ban:1Step 4: Verify Firewall Blocking
# Check banned IPs in nftables blacklist
nft list set ip crowdsec crowdsec-blacklists
# Or check for a specific IP
nft list set ip crowdsec crowdsec-blacklists | grep YOUR_TEST_IPNote: To see blocked IPs, use nft list set ip crowdsec crowdsec-blacklists. To check if table exists without listing all IPs, use nft list tables | grep crowdsec.
From the test machine, try connecting again:
curl -k https://mail.yourdomain.com:25443/login.phpExpected:
- Connection timeout or refused
- No HTTP response
- Blocked at the network level! ✅
Step 5: Monitor Real Attacks
# View recent bans
cscli alerts list
# View current decisions (active bans)
cscli decisions list
# View alerts for specific scenario
cscli alerts list | grep "my/postfix-bf"Real-world example:
╭────────┬──────────┬───────────────────┬────────────────────┬────────┬─────────╮
│ ID │ Source │ Scope:Value │ Reason │ Action │ Country │
├────────┼──────────┼───────────────────┼────────────────────┼────────┼─────────┤
│ 267695 │ crowdsec │ Ip:143.47.189.215 │ my/postfixadmin-bf │ ban │ NL │
│ 260846 │ crowdsec │ Ip:185.196.11.30 │ my/postfix-bf │ ban │ CH │
│ 254002 │ crowdsec │ Ip:178.16.54.15 │ my/postfix-bf │ ban │ NL │
╰────────┴──────────┴───────────────────┴────────────────────┴────────┴─────────╯
Step 6: Verify IPv4 and IPv6 Protection
After running the test script from Step 3, check that both IPv4 and IPv6 addresses are banned:
# Check decisions list on mail server
cscli decisions listExpected result:
╭────────┬──────────┬────────────────────────────┬────────────────────┬────────┬─────────╮
│ ID │ Source │ Scope:Value │ Reason │ Action │ Country │
├────────┼──────────┼────────────────────────────┼────────────────────┼────────┼─────────┤
│ 267696 │ crowdsec │ Ip:2603:c022:c002:2701::12 │ my/postfixadmin-bf │ ban │ NL │ ←IPv6
│ 267695 │ crowdsec │ Ip:143.47.189.215 │ my/postfixadmin-bf │ ban │ NL │ ←IPv4
╰────────┴──────────┴────────────────────────────┴────────────────────┴────────┴─────────╯
Both IPv4 and IPv6 addresses from the same source get banned! ✅
This confirms CrowdSec properly detects and blocks attacks from both address families.
Monitoring and Maintenance
Daily Monitoring Commands
# Check for new alerts today
cscli alerts list --since 1d
# View active bans
cscli decisions list
# Check metrics summary
cscli metricsView Specific Scenario Activity
# Dovecot brute-force attempts
cscli alerts list | grep "my/dovecot-bf"
# Postfix SMTP AUTH attacks
cscli alerts list | grep "my/postfix-bf"
# PostfixAdmin login attempts
cscli alerts list | grep "my/postfixadmin-bf"Real-Time Monitoring
# Watch for new bans
watch -n 5 'cscli alerts list | head -15'
# Monitor CrowdSec logs
journalctl -u crowdsec -f
# Monitor bouncer activity
journalctl -u crowdsec-firewall-bouncer -fCheck Community Blocklist (CAPI)
CrowdSec shares threat intelligence. Check what you’re getting from the community:
# View CAPI decisions
cscli decisions list | grep CAPI
# Check console enrollment status
cscli console statusExpected output:
╭────────────────────┬───────────┬──────────────────────────────────────────────────────╮
│ Option Name │ Activated │ Description │
├────────────────────┼───────────┼──────────────────────────────────────────────────────┤
│ custom │ ✅ │ Forward alerts from custom scenarios to the console │
│ manual │ ❌ │ Forward manual decisions to the console │
│ tainted │ ✅ │ Forward alerts from tainted scenarios to the console │
│ context │ ❌ │ Forward context with alerts to the console │
│ console_management │ ❌ │ Receive decisions from console │
╰────────────────────┴───────────┴──────────────────────────────────────────────────────╯
Default enrollment: CrowdSec automatically enrolls and shares alerts from custom scenarios (✅). This helps the community detect new threats!
Manually Ban/Unban IPs
# Manually ban an IP
cscli decisions add --ip 1.2.3.4 --duration 24h --reason "Manual ban"
# Unban an IP
cscli decisions delete --ip 1.2.3.4
# Ban an IP range
cscli decisions add --range 1.2.3.0/24 --duration 48h --reason "Malicious range"Flush Old Decisions
# Remove expired bans (automatic, but can force)
cscli decisions delete --all-expiredTuning and Optimization
Adjust Ban Durations
If you’re getting too many bans or want longer protection:
Edit scenario file:
vi /etc/crowdsec/scenarios/s00-user/postfix-bf.yamlModify blackhole (ban duration):
blackhole: 24h # Change from 4h to 24h for longer bansReload:
systemctl reload crowdsecAdjust Sensitivity
More aggressive (ban faster):
capacity: 2 # Ban after 2 attempts instead of 3
leakspeed: "12h" # Slower leak = attacks accumulate easierLess aggressive (avoid false positives):
capacity: 5 # Ban after 5 attempts instead of 3
leakspeed: "4h" # Faster leak = need faster attacks to triggerWhitelist Trusted IPs
# Whitelist your own IP
cscli decisions add --ip YOUR_IP --type whitelist --reason "Admin IP"
# Whitelist your office network
cscli decisions add --range 192.168.1.0/24 --type whitelist --reason "Office network"
# View whitelisted IPs
cscli decisions list --type whitelistPerformance Optimization
If CrowdSec uses too much CPU/RAM:
- Reduce log verbosity (if needed):
# Edit config
vi /etc/crowdsec/config.yaml
# Set log level
log_level: info # Options: trace, debug, info, warning, error- Limit acquisition (if processing too many logs):
# Check what's using resources
cscli metrics | grep -A5 "Acquisition"- Disable unused collections:
# Remove collections you don't need
cscli collections remove crowdsecurity/linux
# Reload
systemctl reload crowdsecTroubleshooting
Parser Not Working
Symptom: Lines unparsed = high, no events reaching scenarios
Debug:
# Test with real log line (complete syslog format)
echo 'Dec 06 10:00:00 mail dovecot: auth-worker(user,1.2.3.4)<123>: sql: unknown user' > /tmp/test.log
cscli explain --file /tmp/test.log --type syslog --verbose
rm /tmp/test.log
# Check parser is loaded
cscli parsers list | grep dovecotSolution:
- Verify parser is in
/etc/crowdsec/parsers/s01-parse/(NOTs00-user!) - Check YAML syntax:
cscli parsers inspect my/dovecot-2.4 - Reload:
systemctl reload crowdsec
Scenario Not Triggering
Symptom: Attacks in logs but no bans issued
Debug:
# Check scenario is loaded
cscli scenarios list | grep "my/postfix-bf"
# Check if events reaching scenario
cscli metrics | grep -A5 "Bucket Metrics"
# Test scenario filter with real log file
cscli explain --file /var/log/mail.log --type syslog --verbose
# Or test with a single line (must include syslog header)
echo 'Dec 06 10:00:00 mail postfix/smtpd[12345]: warning: unknown[1.2.3.4]: SASL LOGIN authentication failed' > /tmp/test.log
cscli explain --file /tmp/test.log --type syslog --verbose
rm /tmp/test.logCommon issues:
- ❌ Filter doesn’t match log format
- ❌ Parser not extracting required fields
- ❌ Capacity too high (not reaching threshold)
Solution:
- Test filter against actual logs
- Adjust capacity/leakspeed
- Check parser output includes required fields
Firewall Not Blocking
Symptom: Ban issued but traffic still getting through
Debug:
# Check bouncer is running
systemctl status crowdsec-firewall-bouncer
# Verify IP is in nftables
nft list set ip crowdsec crowdsec-blacklists | grep <IP>
# Check bouncer logs
journalctl -u crowdsec-firewall-bouncer -n 50Common issues:
- ❌ Behind reverse proxy (see Architecture Considerations)
- ❌ Bouncer not running
- ❌ nftables rules not applied
Solution:
- If behind proxy: Move CrowdSec to proxy or use direct access
- Restart bouncer:
systemctl restart crowdsec-firewall-bouncer - Verify table exists:
nft list tables | grep crowdsec
CrowdSec Won’t Start
Symptom: Service fails to start
Debug:
# Check detailed error
journalctl -xeu crowdsec
# Test configuration
crowdsec -c /etc/crowdsec/config.yaml -t
# Check YAML syntax
cscli scenarios listCommon errors:
- Syntax error in YAML file
- Missing required fields in scenario
- Invalid collection reference
Solution:
- Check the error message for the file/line number
- Validate YAML syntax online
- Remove the problematic scenario and reload
High CPU/Memory Usage
Symptom: CrowdSec is using excessive resources
Debug:
# Check what's processing
cscli metrics
# Check CrowdSec resource usage
systemctl status crowdsec
# View recent CPU/memory usage
top -b -n 1 | grep crowdsec
# Check log processing activity
journalctl -u crowdsec -n 50 | grep -E "parsed|scenario"Solutions:
- Reduce log sources in acquis.yaml
- Increase polling interval
- Disable unused parsers/scenarios
- Check for log loops (e.g., CrowdSec logging to journal and reading journal)
Restart Resets Buckets
Symptom: Counters reset after CrowdSec restart
Expected behavior: This is normal! CrowdSec buckets are in-memory and don’t persist across restarts.
Impact: Attackers need to start over after CrowdSec restart (bucket resets to 0).
This is by design – not a bug.
Summary and Next Steps
What You Now Have
✅ Complete mail server protection:
- IMAP/POP3 brute-force detection (Dovecot)
- SMTP AUTH brute-force detection (Postfix)
- Web interface protection (PostfixAdmin)
✅ Proper Dovecot 2.4 support (Debian 13)
✅ IPv4 and IPv6 protection
✅ Catches slow AND fast attacks
✅ Community threat intelligence (CAPI)
✅ Battle-tested configurations
Real-World Results
From production deployment:
24 hours after deployment:
- 5+ attackers detected and banned
- 50+ authentication attempts blocked
- 0 false positives
- 100% uptimeAppendix: Quick Reference
Essential Commands
# Status
systemctl status crowdsec
cscli metrics
# Scenarios
cscli scenarios list
cscli scenarios list | grep "my/"
# Alerts & Bans
cscli alerts list
cscli decisions list
# Manual Ban/Unban
cscli decisions add --ip 1.2.3.4 --duration 24h
cscli decisions delete --ip 1.2.3.4
# Reload Configuration
systemctl reload crowdsec
# Test Configuration
crowdsec -c /etc/crowdsec/config.yaml -t
# Debug Log Line (must include syslog header: timestamp hostname program: message)
echo 'Dec 06 10:00:00 mail dovecot: auth-worker(user,1.2.3.4)<123>: unknown user' > /tmp/test.log
cscli explain --file /tmp/test.log --type syslog --verbose
rm /tmp/test.log
File Locations
/etc/crowdsec/
├── config.yaml # Main config
├── acquis.d/ # Log acquisition
│ └── apache2.yaml
├── parsers/
│ └── s01-parse/ # Custom parsers (NOT s00-user!)
│ └── dovecot-2.4.yaml
└── scenarios/
└── s00-user/ # Custom scenarios
├── dovecot-bf.yaml
├── postfix-bf.yaml
└── postfixadmin-bf.yaml
Scenario Settings Comparison
| Service | Capacity | Leakspeed | Blackhole | Why |
|---|---|---|---|---|
| Dovecot | 3 | 8h | 4h | Mail attacks slow |
| Postfix | 3 | 8h | 4h | SMTP AUTH attacks slow |
| PostfixAdmin | 5 | 2h | 2h | Web users make typos |
Attack Types Detected
| Attack Type | Scenario | Example |
|---|---|---|
| IMAP/POP3 brute-force | my/dovecot-bf | Password guessing |
| SMTP AUTH brute-force | my/postfix-bf | Username enumeration |
| PostfixAdmin login | my/postfixadmin-bf | Admin panel attacks |
| Spam attempts | postfix-spam | Mass mailing |
| Relay abuse | postfix-relay-denied | Open relay probing |
Next Steps
Critical next step: Proceed immediately with Part 4: Rspamd Mail Filtering and Spam Protection to complete your mail server security:
- Advanced spam filtering with machine learning
- DMARC, SPF, and DKIM validation for incoming mail
- Greylisting to block spam waves
- Virus and malware scanning integration
- Rate limiting to prevent abuse
- Reputation scoring and adaptive filtering
Your mail server is now protected from intrusion attempts, but incoming spam and malware can still reach user mailboxes. Rspamd provides essential content filtering and reputation management before accepting production email traffic.
Return to: Mail Server Series Index
