Your Server Is Smarter Than fail2ban
fail2ban is one of those tools every self-hoster installs once, forgets about, and assumes is handling things. And honestly? It does handle things — badly scanning SSH brute-forcers who hammer port 22 from a single IP, sure. But here’s the problem: fail2ban is completely blind. It only knows about threats that have already hit your logs. Some IP scanning servers in Vietnam right now? fail2ban has no idea. It’ll learn, eventually, when that scanner reaches you. Probably at 2 AM.
CrowdSec changes the model. Instead of every server learning independently from its own log wounds, CrowdSec shares threat intelligence across its entire user base. When a scanner hits someone else’s Nginx instance in Germany, your server knows about it within minutes. You’re not defending alone anymore.
Here’s what that means in practice: a fresh CrowdSec install blocks millions of IPs before a single packet hits your app. Not because you configured it perfectly — because 400,000 other CrowdSec instances already did the work for you.
How CrowdSec Actually Works
Three moving parts:
Agent — Reads your logs, runs them through scenario rules (called collections), and makes ban decisions locally. Runs as a service on your host.
Collections — Curated sets of parsers + scenarios. crowdsecurity/nginx teaches the agent what an Nginx log looks like and what a brute-force pattern looks like in that format. There are collections for sshd, traefik, caddy, http-cve exploits, WordPress, and more. You install the ones that match your stack.
Bouncers — The enforcement layer. The agent makes a decision (ban this IP). A bouncer acts on that decision at whatever layer you choose: iptables/nftables, Caddy middleware, Traefik middleware, Cloudflare WAF, HAProxy, etc. The agent and bouncer are deliberately separate — you can ban at the firewall level or at the application level, depending on what makes sense.
The central API (LAPI) glues it together locally and also connects to CrowdSec’s cloud-based blocklist. The community blocklist is free and updated in near-real-time.
Installing the Agent
The official repos make this straightforward on Debian/Ubuntu:
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bashsudo apt install crowdsecFor RPM-based distros:
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.rpm.sh | sudo bashsudo dnf install crowdsecAfter install, the agent starts automatically and reads /etc/crowdsec/acquis.yaml to know which log files to watch.
Check that it’s running:
sudo systemctl status crowdsecsudo cscli versionPicking Your Collections
Collections are installed with cscli. List what’s already on your system and what’s available:
sudo cscli collections listsudo cscli hub list -t collectionsFor a typical self-hosted stack running public web services, start here:
sudo cscli collections install crowdsecurity/nginxsudo cscli collections install crowdsecurity/sshdsudo cscli collections install crowdsecurity/http-cvesudo cscli collections install crowdsecurity/traefik # if you use traefiksudo cscli collections install crowdsecurity/caddy # if you use caddycrowdsecurity/http-cve is the one you don’t want to skip. It watches for known CVE exploitation attempts — WordPress plugin scanners, Log4Shell probes, path traversal, shellshock — and blocks them regardless of which web server is fronting your app.
After installing collections, reload:
sudo systemctl reload crowdsecTelling CrowdSec Where Your Logs Are
The acquis.yaml file tells the agent what to read. The default covers syslog and journald, but you’ll want to add your specific log paths:
# /etc/crowdsec/acquis.yaml
# SSH via journald (works without a log file)- source: journald journalctl_filter: - "_SYSTEMD_UNIT=ssh.service" labels: type: syslog
# Nginx access logs- filenames: - /var/log/nginx/access.log - /var/log/nginx/error.log labels: type: nginx
# Traefik via Docker- source: docker container_name: - traefik labels: type: traefik
# Caddy (if logging to file)- filenames: - /var/log/caddy/access.log labels: type: caddyReload after editing:
sudo systemctl reload crowdsecVerify the agent is parsing logs correctly:
sudo cscli metricsYou’ll see lines parsed, events triggered, and decisions made. If the parsed count is zero, your log path is wrong or the label type doesn’t match the installed collection.
Docker Setup
Running everything in Docker? You can run the CrowdSec agent in a container alongside your stack:
services: crowdsec: image: crowdsecurity/crowdsec:latest container_name: crowdsec restart: unless-stopped environment: - COLLECTIONS=crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/sshd - GID=${GID:-1000} volumes: - ./crowdsec/config:/etc/crowdsec - ./crowdsec/data:/var/lib/crowdsec/data # Mount log files or Docker socket for log acquisition - /var/log:/var/log:ro - /var/run/docker.sock:/var/run/docker.sock:ro networks: - proxy
traefik: image: traefik:v3.0 container_name: traefik restart: unless-stopped ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik/config:/etc/traefik - ./traefik/logs:/logs networks: - proxy
# CrowdSec bouncer as Traefik plugin (traefik-bouncer) bouncer-traefik: image: fbonalair/traefik-crowdsec-bouncer:latest container_name: bouncer-traefik restart: unless-stopped environment: - CROWDSEC_BOUNCER_API_KEY=${CROWDSEC_BOUNCER_API_KEY} - CROWDSEC_AGENT_HOST=crowdsec:8080 networks: - proxy
networks: proxy: external: trueThe COLLECTIONS env var tells the container to install and enable those collections on first boot. You still need acquis.yaml to tell it where the logs live — mount a config directory and drop the file in there.
Setting Up a Bouncer
The agent bans IPs — bouncers enforce those bans. Pick the right bouncer for your layer.
iptables/nftables Bouncer (host-level)
Best for bare-metal or VM setups where you want bans at the network layer:
sudo apt install crowdsec-firewall-bouncer-iptablesIt reads decisions from the local API and inserts/removes iptables rules automatically. Check its status:
sudo systemctl status crowdsec-firewall-bouncerTraefik Bouncer (middleware)
For Docker-based setups with Traefik, use the Traefik plugin approach. First, generate a bouncer API key:
sudo cscli bouncers add traefik-bouncer# Outputs: API key — copy this, you won't see it againAdd the bouncer container (as in the compose above) and configure Traefik to use it as a forward-auth middleware:
http: middlewares: crowdsec-bouncer: forwardAuth: address: "http://bouncer-traefik:8080/api/v1/forwardAuth" trustForwardHeader: true
routers: my-service: rule: "Host(`app.example.com`)" middlewares: - crowdsec-bouncer service: my-serviceEvery request goes through the bouncer container, which checks CrowdSec’s decision list and returns 403 for banned IPs before Traefik ever forwards the request.
Caddy Bouncer
CrowdSec has a native Caddy module. Add it via xcaddy:
xcaddy build --with github.com/hslatman/caddy-crowdsec-bouncer/httpThen in your Caddyfile:
{ crowdsec { api_url http://localhost:8080 api_key YOUR_BOUNCER_API_KEY ticker_interval 15s }}
app.example.com { crowdsec reverse_proxy localhost:3000}The crowdsec directive tells Caddy to check every request against the decision list.
Reading the cscli Output
Once things are running, cscli is your main diagnostic tool.
Check current bans:
sudo cscli decisions list+--------+----------+---------+------------------------------------+--------+| ID | SOURCE | SCOPE:VALUE | REASON | ACTION|+--------+----------+---------+------------------------------------+--------+| 123456 | crowdsec | Ip:185.220.x.x | crowdsecurity/ssh-slow-brute | ban || 123457 | CAPI | Ip:91.108.x.x | community-blocklist | ban |+--------+----------+---------+------------------------------------+--------+The SOURCE: CAPI rows are bans from the community blocklist — IPs that other CrowdSec instances flagged, pushed to the central API, and distributed to you. You didn’t have to see that traffic to block it.
Check alerts (triggered scenarios):
sudo cscli alerts listCheck which parsers and scenarios are active:
sudo cscli parsers listsudo cscli scenarios listManually ban an IP (useful for testing your bouncer):
sudo cscli decisions add --ip 1.2.3.4 --duration 1h --reason "testing bouncer"sudo cscli decisions delete --ip 1.2.3.4Check the community blocklist subscription:
sudo cscli capi statusIf you see You can successfully interact with Central API you’re pulling the community blocklist. If not, check your internet connectivity and that the agent can reach api.crowdsec.net.
Enrolling with CrowdSec Console (Optional but Useful)
CrowdSec has a free web console at app.crowdsec.net that gives you dashboards, blocklist management, and the ability to add premium blocklists (threat actors by category, geography, etc.).
Enroll your instance:
sudo cscli console enroll YOUR_ENROLLMENT_KEYsudo systemctl reload crowdsecGet the key from app.crowdsec.net → Instances → Add. Once enrolled you get a nice dashboard showing decisions, alerts, and community contribution stats. The “contributing” status matters — the more CrowdSec instances share data, the better everyone’s blocklist gets.
Tuning: Whitelists and False Positives
CrowdSec will occasionally ban Googlebot, your monitoring system, or your own IP. Whitelist with:
sudo cscli decisions delete --ip 203.0.113.5 # one-off unbanFor permanent whitelists, add to /etc/crowdsec/parsers/s02-enrich/whitelists.yaml:
name: custom/whitelistsdescription: "My trusted IPs"whitelist: reason: "Internal and monitoring IPs" ip: - "192.168.1.0/24" - "10.0.0.0/8" cidr: - "203.0.113.0/24"Reload and the agent won’t trigger decisions against those ranges.
fail2ban vs CrowdSec: When to Use Which
Switch to CrowdSec when:
- You’re running public-facing services (web apps, APIs, anything on 80/443)
- You want community threat intel without manual IP blocklist management
- You use Traefik or Caddy and want native middleware integration
- You have multiple servers — CrowdSec scales, fail2ban doesn’t
- You’re dealing with distributed attacks where the same campaign hits you from hundreds of IPs (fail2ban bans them one by one; CrowdSec blocks the entire campaign pattern)
Stick with fail2ban when:
- You have a very minimal setup (single server, SSH only, no web traffic)
- You’re locked into an extremely old distro where CrowdSec’s agent won’t run
- You need regex-based custom rules and don’t want to learn CrowdSec’s YAML scenario format
- You genuinely just need “ban after 5 failed SSH logins” and nothing else
Honestly, for anything running public web services in 2026, CrowdSec is the correct choice. fail2ban is fine for what it is — a local log watcher with ban rules. CrowdSec is that plus a network-wide immune system. The install takes 15 minutes. The community blocklist is immediately useful. The bouncer integration with Traefik or Caddy is clean.
Your 2 AM self, staring at 40,000 blocked requests from a botnet you never had to configure rules for, will appreciate it.