fail2ban was designed for a simpler threat model: SSH brute force from a handful of fixed IPs. CrowdSec assumes adversaries are distributed, scripted, and reusing infrastructure across thousands of victims. The architectural differences really do matter:
- Decoupled detection and response. The CrowdSec Security Engine only detects. Blocking is delegated to bouncers — pluggable agents that enforce decisions in nftables, iptables, Nginx, Traefik, Cloudflare, AWS WAF, or your application. Detection runs on the same host or remotely; bouncers run wherever enforcement makes sense.
- Multi-source parsing with grok + YAML scenarios. A single CrowdSec instance can ingest journald, file-based logs, syslog, Kubernetes audit logs, Windows Event Logs, and AWS CloudTrail in parallel. Scenarios are leaky-bucket state machines, not single regex matches, so they handle the slow-and-low attacks fail2ban tends to miss.
- Crowdsourced blocklist (CAPI). Every signal a community installation generates gets shared (after privacy filtering) with the Central API. Subscribers pull a curated, low-false-positive blocklist of IPs that have already attacked others — typically 60,000–120,000 active hostile IPs at any moment in 2026.
- Multi-server architecture. One LAPI (Local API) instance can serve dozens of remote agents and bouncers, giving you a fleet-wide decision plane without rolling your own SIEM glue.
- Hub of community content. Over 200 maintained collections cover Nginx, Apache, MySQL, MongoDB, Postfix, WordPress, GitLab, Vaultwarden, Home Assistant, and more — versioned and cryptographically signed.
Architecture in One Diagram (Conceptually)
Before installing anything, fix the data flow in your head. There are four moving parts:
- Acquisition reads logs from files, journald, syslog, or other sources and feeds events into the engine.
- Parsers normalize raw lines into structured events (extracting
source_ip, http_status, program, etc.) using grok patterns plus YAML stages.
- Scenarios consume parsed events through leaky buckets. When a bucket overflows (e.g., 5 failed SSH logins in 60 seconds from one IP), an alert and a decision are emitted.
- LAPI stores decisions in SQLite or PostgreSQL and exposes them over an authenticated HTTP API. Bouncers poll LAPI and enforce.
This separation is, hands down, the single most important conceptual difference from fail2ban. Once it clicks, every configuration file makes sense.
Installing CrowdSec on Debian, Ubuntu, and RHEL
CrowdSec ships its own repositories with signed packages. Skip the distribution packages — they lag behind the upstream hub and break collection updates (learned that one the hard way).
Debian 12 / Ubuntu 22.04 / 24.04
curl -s https://install.crowdsec.net | sudo sh
sudo apt update
sudo apt install -y crowdsec
sudo systemctl enable --now crowdsec
sudo cscli version
RHEL 9 / Rocky / AlmaLinux
curl -s https://install.crowdsec.net | sudo sh
sudo dnf install -y crowdsec
sudo systemctl enable --now crowdsec
sudo cscli version
The installer auto-detects systemd units and pre-configures journald acquisition for SSH and the default web servers it finds. Verify with:
sudo cscli metrics
sudo cscli collections list
sudo cscli machines list
sudo cscli bouncers list
You should see a crowdsecurity/linux collection installed, the local LAPI machine registered, and zero bouncers (we'll add them in the next section).
Installing Your First Bouncer: cs-firewall-bouncer with nftables
Detection without enforcement is just glorified logging. The most flexible bouncer for a Linux host is cs-firewall-bouncer, which programs nftables (or iptables/ipset) directly from LAPI decisions.
sudo apt install -y crowdsec-firewall-bouncer-nftables
sudo systemctl status crowdsec-firewall-bouncer.service
The package installs an API key automatically and registers a bouncer with LAPI. Verify both sides:
sudo cscli bouncers list
sudo nft list table inet crowdsec
The bouncer creates two sets — crowdsec-blacklists (IPv4) and crowdsec6-blacklists (IPv6) — and inserts a drop rule before your existing input chain. Behavior is configured in /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml:
mode: nftables
update_frequency: 10s
log_level: info
log_mode: file
nftables:
ipv4:
enabled: true
set-only: false
table: crowdsec
chain: crowdsec-chain
priority: -10
ipv6:
enabled: true
set-only: false
table: crowdsec6
chain: crowdsec6-chain
priority: -10
deny_action: DROP
deny_log: true
deny_log_prefix: "crowdsec-drop: "
If you already manage nftables with a custom ruleset (for example, the nftables hardening guide on this site), set set-only: true and add your own rule that references the CrowdSec set:
table inet filter {
set crowdsec-blacklists {
type ipv4_addr
flags timeout
}
chain input {
type filter hook input priority 0; policy drop;
ip saddr @crowdsec-blacklists drop
# ... rest of your rules
}
}
This avoids a second table and gives CrowdSec a clean enforcement point inside your existing policy. Cleaner. Easier to audit.
Subscribing to the Community Blocklist
The most immediate value of CrowdSec? The Central API blocklist. Even before you tune a single scenario, your firewall will start dropping IPs that have already brute-forced or scanned other CrowdSec users in the last few hours.
sudo cscli capi register
sudo systemctl reload crowdsec
sudo cscli capi status
sudo cscli decisions list --origin CAPI | head
Within a minute or two, tens of thousands of decisions appear. The community blocklist is rate-limited and quality-filtered — IPs are only included after consensus from multiple installations and are aged out aggressively, so false positives stay below 0.1% in practice.
Subscription Blocklists for Higher-Confidence Coverage
A free CrowdSec console account at app.crowdsec.net unlocks several curated third-party blocklists (Firehol, Tor exit nodes, known bot networks). Enroll the engine:
sudo cscli console enroll <your-enroll-key>
sudo cscli console status
From the console UI you can subscribe to additional blocklists, and the engine pulls them automatically every few minutes. This step is optional, but it takes 60 seconds and meaningfully improves coverage of credential-stuffing infrastructure. Worth doing.
Hub Collections: One-Command Detection for Common Services
The CrowdSec Hub packages parsers and scenarios into collections. Installing a collection is the equivalent of writing dozens of fail2ban filters and jails in a single command — and it's one of those moments where you realize how much manual glue you've been writing for years.
sudo cscli hub update
sudo cscli collections install crowdsecurity/sshd
sudo cscli collections install crowdsecurity/nginx
sudo cscli collections install crowdsecurity/base-http-scenarios
sudo cscli collections install crowdsecurity/http-cve
sudo cscli collections install crowdsecurity/postfix
sudo cscli collections install crowdsecurity/iptables
sudo systemctl reload crowdsec
The http-cve collection is particularly valuable: it ships scenarios for active exploit attempts against published CVEs (Log4Shell, Spring4Shell, Confluence OGNL, Atlassian path traversal, recent GitLab and Jenkins issues) and is updated by the maintainers within hours of a new exploit going public. That's the kind of thing you simply can't replicate manually.
Audit what you have running:
sudo cscli collections list
sudo cscli scenarios list
sudo cscli parsers list
Acquisition: Telling CrowdSec Where to Read From
Acquisition files in /etc/crowdsec/acquis.d/ describe log sources. CrowdSec ships sane defaults; here's a journald-based config that covers SSH, sudo, and Nginx without touching files:
# /etc/crowdsec/acquis.d/journald.yaml
source: journalctl
journalctl_filter:
- "_SYSTEMD_UNIT=ssh.service"
labels:
type: syslog
---
source: journalctl
journalctl_filter:
- "_SYSTEMD_UNIT=nginx.service"
labels:
type: nginx
---
source: journalctl
journalctl_filter:
- "SYSLOG_IDENTIFIER=sudo"
labels:
type: syslog
The type label selects the parser pipeline. type: syslog hits the syslog parser and then SSH-specific scenarios; type: nginx hits the Nginx parser and the HTTP scenarios. After editing, run sudo systemctl restart crowdsec and check journalctl -u crowdsec --since "5 min ago" for parsing errors. (Tail it for a minute — typos here are silent until you hit a real attack.)
Writing a Custom Scenario
Hub collections cover the obvious cases. The first time you'll need a custom rule is usually for a homegrown application or a SaaS-style endpoint exposed on your perimeter. Scenarios live in /etc/crowdsec/scenarios/, and here's a complete example that bans an IP after 10 HTTP 401 responses against your custom /api/login endpoint within two minutes:
# /etc/crowdsec/scenarios/api-login-bf.yaml
type: leaky
name: myorg/api-login-bf
description: "Brute-force on /api/login"
filter: |
evt.Meta.log_type == 'http_access-log' &&
evt.Parsed.request matches '^/api/login' &&
evt.Meta.http_status == '401'
groupby: evt.Meta.source_ip
distinct: evt.Meta.source_ip
capacity: 10
leakspeed: "12s"
blackhole: 5m
labels:
service: api
type: bruteforce
remediation: true
The math, in case you're new to leaky buckets: capacity: 10 with leakspeed: 12s means 10 events allowed, draining one per 12 seconds. Eleven 401s within ~120 seconds overflow the bucket and emit a decision. blackhole: 5m prevents the same IP from generating duplicate alerts for five minutes. Validate the scenario before relying on it:
sudo cscli scenarios list | grep api-login
sudo systemctl reload crowdsec
# Replay a stored Nginx log to confirm matching:
sudo cscli explain --file /var/log/nginx/access.log.1 --type nginx
The cscli explain command is your best friend when authoring scenarios — it shows exactly which parsers and scenarios fire on each line. I keep a terminal open with it permanently when I'm tuning rules.
Multi-Server Deployment with Remote LAPI
For a fleet, one host runs the LAPI and database; every other host runs only the agent and ships alerts upstream. This avoids per-host blocklists drifting out of sync and centralizes the console view.
On the LAPI server
# Make LAPI listen on all interfaces (or a private one)
sudo sed -i 's|listen_uri: 127.0.0.1:8080|listen_uri: 0.0.0.0:8080|' \
/etc/crowdsec/config.yaml
sudo systemctl restart crowdsec
# Issue an enrollment token for a remote agent
sudo cscli machines add agent-web01 --auto
The output prints credentials. Front the API with TLS (Caddy or Nginx terminating to 127.0.0.1:8080) before exposing it across networks — the LAPI assumes a trusted transport, so don't skip this.
On each remote agent
sudo cscli lapi register --machine agent-web01 --url https://lapi.internal:8080
# Approve on the LAPI:
sudo cscli machines validate agent-web01
The remote agent now ships alerts to the central LAPI, decisions are stored centrally, and any bouncer (local or remote) sees the unified set.
Migrating from fail2ban
You can run both side-by-side during a transition, but eventually you want one source of truth. A clean migration takes about an hour for a typical server:
- Inventory your jails.
sudo fail2ban-client status lists active jails. For each one, find the matching CrowdSec collection (most map directly: sshd, nginx-http-auth, postfix-sasl, recidive).
- Install the equivalent collections with
cscli collections install and reload CrowdSec.
- Run both engines for 48 hours and compare.
cscli alerts list --since 24h shows what CrowdSec caught; fail2ban-client status <jail> shows what fail2ban caught. Coverage gaps are usually custom regex jails — port them as scenarios.
- Switch enforcement. Stop fail2ban (
sudo systemctl disable --now fail2ban) and confirm cs-firewall-bouncer is the sole owner of blocking rules.
- Clean up:
sudo iptables -F f2b-sshd for any stale chains, then apt purge fail2ban.
One frequent gotcha: fail2ban's recidive jail (banning IPs that get banned by other jails) maps to CrowdSec's blackhole + scenarios with longer durations, not a separate scenario. The hub collection crowdsecurity/iptables implements similar escalation logic out of the box.
Observability: Metrics, Console, and Dashboards
CrowdSec exposes Prometheus metrics on :6060/metrics by default. Scrape it with your existing Prometheus and add the official Grafana dashboard (ID 13935) for a panel of buckets, scenarios, decisions, and parser performance.
sudo cscli metrics
curl -s http://127.0.0.1:6060/metrics | grep cs_bucket
The community console (app.crowdsec.net) gives you an alert feed, blocklist subscriptions, and engine inventory across hosts at no cost. For air-gapped fleets, every console feature has a CLI equivalent — nothing forces you to use the SaaS.
Hardening CrowdSec Itself
CrowdSec runs as the crowdsec user via a systemd unit that already applies most hardening directives. A short audit:
systemd-analyze security crowdsec.service
You should see a score below 2.0 ("OK"). If you front LAPI with Nginx, harden the reverse proxy (limit_req, mTLS for agent connections, IP allowlist for the management endpoints) and set api.server.trusted_proxies in /etc/crowdsec/config.yaml so client IPs are extracted from X-Forwarded-For:
api:
server:
trusted_proxies:
- 127.0.0.1/32
- 10.0.0.0/8
use_forwarded_for_header: true
Rotate bouncer API keys quarterly with cscli bouncers add <name> --key <new-key> and update /etc/crowdsec/bouncers/*.yaml on each enforcement node. Set a calendar reminder — it's the kind of thing nobody remembers to do until the audit.
Common Operational Commands Cheat Sheet
# Show current decisions (active bans)
sudo cscli decisions list
# Manually ban an IP for 1 hour
sudo cscli decisions add --ip 198.51.100.42 --duration 1h --reason "manual"
# Allow an IP permanently (whitelist)
sudo cscli decisions delete --ip 203.0.113.10
sudo cscli alerts inspect <alert-id>
# Replay logs without enforcing (dry run)
sudo cscli explain --file /var/log/auth.log --type syslog
# Update everything in the hub
sudo cscli hub update && sudo cscli hub upgrade && sudo systemctl reload crowdsec
# Validate config before restart
sudo crowdsec -c /etc/crowdsec/config.yaml -t
Frequently Asked Questions
Is CrowdSec better than fail2ban?
For modern, distributed attacks, yes. CrowdSec parses more sources (HTTP, journald, Kubernetes, cloud), uses leaky-bucket scenarios that catch the slow-and-low brute force fail2ban's per-line regexes miss, and pulls a community blocklist of IPs already attacking others. fail2ban remains adequate for a single-purpose SSH jump host with no internet-facing apps, but for any server exposing web, mail, or APIs, CrowdSec is the stronger default in 2026.
Does CrowdSec send my logs to the cloud?
No. The Security Engine processes logs locally and only sends signals — the source IP plus the scenario name that fired — to the Central API after privacy filtering. Log content, user identifiers, request bodies, and internal IPs never leave the host. The full data dictionary is published in CrowdSec's privacy policy and reviewable in the open-source agent code.
How much memory and CPU does CrowdSec use?
A typical single-host install with 10 collections active uses 60–120 MB RSS and well under 1% CPU on a modern server. The agent is written in Go and is dramatically lighter than fail2ban under load (fail2ban's Python regex engine becomes a bottleneck above ~1000 events per second; CrowdSec handles tens of thousands).
Can CrowdSec block at the application layer, not just the firewall?
Yes. Bouncers exist for Nginx (lua), Traefik, HAProxy, Caddy, Apache, PHP, WordPress, AWS WAF, Cloudflare, and a generic Go SDK for custom apps. Application-layer bouncers can return CAPTCHAs, custom HTTP 403 pages, or rate-limit responses instead of dropping the TCP connection — useful when you want to keep good users routed but slow down abuse.
What happens if the LAPI is unreachable?
Bouncers cache the last decision set locally and continue enforcing existing blocks. Agents queue alerts and ship them when LAPI comes back. The community blocklist refresh stops, so net-new global threats won't be blocked until connectivity returns, but no traffic is incorrectly allowed and no existing bans are dropped. For high availability, run two LAPI instances behind a TCP load balancer with PostgreSQL replication.
Where to Go Next
Pair CrowdSec with the multi-layer IDS strategy elsewhere on this site (AIDE for file integrity, Suricata for network IDS, Wazuh for HIDS) and you have detection at four independent layers — file, host, network, and behavioral — feeding a single decision plane. The next practical step is writing scenarios for your specific applications: every custom auth endpoint, every administrative path, every webhook receiver should have a leaky-bucket guard. CrowdSec's cscli explain plus the hub's reference scenarios make that authoring loop measured in minutes, not hours. Honestly, that feedback loop is the part that'll change how you think about defensive tooling.