What is CrowdSec?#
If you enjoyed this article, you can support the author by becoming a sponsor on Boosty (link in the contacts section).
CrowdSec is a modern open-source security tool built around the idea of a “crowdsourced firewall.” It watches your logs, flags suspicious activity (brute-force attempts, port scans, DDoS, that sort of thing), and automatically blocks or throttles the IP addresses behind it.
The real trick up CrowdSec’s sleeve is sharing threat data across every user of the system, building a collective knowledge base out of everyone’s contributions. So if an attacker hits someone else’s server first, your server can block them before they ever show up at your door.
CrowdSec architecture#
CrowdSec’s architecture is modular, and at a high level splits into two layers:
- Agent (the analysis engine) - chews through log files (Nginx, Traefik, SSH, Postfix, system logs, and so on).
- Bouncers (the blocking modules) - act on decisions like ban, captcha, or throttle to actually grant or deny access (iptables, an Nginx middleware, the Traefik bouncer, etc.).
The Agent’s only job is spotting bad IPs; the bouncers are the ones who actually pull the trigger.
That split is what makes CrowdSec so easy to drop into any infrastructure without having to touch your network rules directly.
How does CrowdSec actually work?#
Let’s break down what’s happening under the hood.
1. Collecting and analyzing logs#
CrowdSec taps into the logs of whatever services you’re running (SSH, Nginx, Traefik, Postfix, system journals). Parsers take care of normalizing all that into one common format.
2. Running it through scenarios#
CrowdSec relies on scenarios - rulesets written in YAML. The ssh-bf scenario, for instance, treats 5 failed login attempts in a minute as an attack.
3. Generating decisions#
When a scenario fires, the agent generates a decision - something like:
ban- block the IP outright.captcha- make them prove they’re human.throttle- slow down their requests.
4. Handing it off to a Bouncer#
Bouncers are the components that actually enforce the decision. A few examples:
- an
iptablesbouncer drops the IP straight into the firewall; - a
traefik-bouncerhooks into Traefik as middleware; - an
nginx-bouncerworks through Nginx ACLs.
5. Collective protection (Crowd Threat Intelligence)#
The agent can also send anonymized attacker data up to a central CrowdSec server, and in return gets back a global list of IPs with a bad reputation - which raises the baseline protection for everyone on the network.
Why CrowdSec is worth using#
- A shared threat database - real-time protection against IPs the community has already flagged.
- Flexibility - dozens of integrations out of the box (Docker, Kubernetes, Traefik, Nginx).
- Low friction - ready-made scenarios for all the usual services.
- DevOps-friendly - fits into CI/CD without a fuss, runs happily in containers.
Why CrowdSec beats Fail2Ban#
1. It’s built on a more modern architecture#
- Fail2Ban is the old classic - written in Python, working at the log-analysis level, blocking IPs through the firewall.
- CrowdSec takes a microservices approach and is written in Go, which makes it both faster and easier to scale.
2. It protects you collectively, not just individually#
- Fail2Ban only ever blocks IPs that are attacking your specific server.
- CrowdSec shares malicious-IP data across its entire user base, building a living reputation database. Your server ends up protected not only from attacks it’s already seen, but from IPs caught attacking someone else entirely.
3. It reads from more than just system logs#
- Fail2Ban is mostly limited to system logs.
- CrowdSec can chew through:
- logs from all sorts of services (Nginx, SSH, Postfix, etc.);
- network flows (Netfilter, nftables);
- cloud service and API integrations.
4. Its scenario system is far more flexible#
- CrowdSec describes attack signatures - brute force, port scanning, and so on - as YAML scenarios.
- Pulling in a new scenario is one click away on the official hub; no hand-rolled filters required.
5. It’s simply faster#
- Fail2Ban doesn’t scale well once the log volume climbs, especially on busy servers.
- CrowdSec, written in Go and processing logs asynchronously, is noticeably faster and lighter on resources.
6. Its modular design pays off#
- CrowdSec cleanly separates the analysis engine (agent) from the enforcement mechanisms (bouncers).
- That means you can freely choose where bans get enforced - iptables, Nginx, Cloudflare, HAProxy, take your pick.
7. It gives you something to actually look at#
- CrowdSec ships a CLI with readable stats, plugs into Metabase for graphs and analytics, and has its own graphical console for keeping an eye on your server (though that one needs a login on the developer’s site).
- Fail2Ban gives you logs and a handful of basic commands - that’s about it.
8. It’s easier to keep up to date#
- CrowdSec has a centralized scenario repository with automatic updates.
- With Fail2Ban, updating filters and regexes for new attack patterns is usually on you.
Bottom line:
CrowdSec isn’t just a shinier Fail2Ban - it’s genuinely a next-generation defense system, one that combines collective intelligence with real performance and flexibility. Pair it with a reverse proxy like Traefik, and you get smart, dynamic protection for any web app, built on local log analysis backed by a shared knowledge base.
How CrowdSec actually catches attacks#
Let’s dig a bit deeper into the detection side of things. CrowdSec uses a three-layer approach to catch both known and brand-new threats:
1. Local decisions#
- CrowdSec can detect and react to attacks in real time, right as they happen, on the box itself. When something suspicious - password guessing, aggressive HTTP probing, whatever - matches one of CrowdSec’s built-in detection scenarios, it fires off a local decision: block that IP, throttle it, whatever the scenario calls for. That means a brand-new, never-seen-before attacker gets shut down immediately, before they can ramp things up, usually just from crunching through log files. Once the decision’s made, its metadata - the attacker’s IP, mainly - gets shared with the rest of the CrowdSec community through its cyber threat intelligence (CTI) system.
2. Remote decisions#
- This is where the global CrowdSec community and its CTI network come in. The moment any CrowdSec instance anywhere on the planet catches an attacker, that intel lands in CrowdSec’s Cyber Threat Intelligence database. From there, known bad IPs get flagged for preemptive blocking across every other CrowdSec-protected system out there, via remote decisions. It’s a genuinely proactive layer - CrowdSec can block a threat based purely on someone else’s bad day, stopping attackers before they’ve even tried knocking on your door. This runs through the CrowdSec bouncer (for the Traefik reverse proxy, in our case).
3. AppSec (the WAF layer)#
- You can also run CrowdSec as middleware acting as a full WAF. Every HTTP request gets relayed to the CrowdSec AppSec endpoint over TCP/7422, which inspects it for malicious patterns - SQL injection payloads, XSS attempts, command injection, that kind of thing. If you want, you can bring in the well-known OWASP core rule set (CRS), the same one behind the open-source ModSecurity WAF and used by plenty of other WAF vendors too. Setting up the WAF properly takes some time, and usually needs tuning per app you’re putting behind it.
As mentioned, local decisions run on Scenarios, which typically ship bundled together as Collections.
Follow this guide (bear with the somewhat grandiose framing) and you’ll end up with over 100 local-decision scenarios running. Your HTTP services behind Traefik will be covered against a solid chunk of the threats out there.
In this guide we’ll use the following collections:
crowdsecurity/traefik traefik support: parser and generic http scenarios crowdsecurity/http-cve Detect CVE exploitation in http logs crowdsecurity/base-http-scenarios http common : scanners detection crowdsecurity/sshd support : parser and brute-force detection crowdsecurity/linux core linux support : syslog+geoip+ssh crowdsecurity/appsec-generic-rules A collection of generic attack vectors for additional protection crowdsecurity/appsec-virtual-patching a generic virtual patching collection, suitable for most web servers. crowdsecurity/appsec-crs Appsec: Modsecurity core rule set rules
Getting Crowdsec running#
Crowdsec can run either natively on a variety of OSes, or as a Docker container. We’ll go the Docker route in this article; running it natively alongside a native Traefik install is a topic for another day.
The Docker Compose file#
Let’s start by putting together a docker-compose.yml with the core Crowdsec settings.
services:
crowdsec: # Service name
image: crowdsecurity/crowdsec:v1.7 # pin to the current stable branch (1.8 is still in release-candidate status as of this update). Don't use latest, for the same reasons as with Traefik - an unannounced image update could bring a breaking change
container_name: crowdsec # container name
environment: # environment variables
GID: "${GID-1000}" # user:group permissions
COLLECTIONS: "crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/base-http-scenarios crowdsecurity/sshd crowdsecurity/linux crowdsecurity/appsec-generic-rules crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-crs" # names of the collections that will be loaded
TZ: Europe/Moscow # timezone. A very important setting, so that event times display correctly
volumes: # volumes required for Crowdsec to work correctly
- /home/path/to/crowdsec/db:/var/lib/crowdsec/data/ # path to the crowdsec database. Starting with version 1.7, this is no longer a recommendation but a mandatory requirement - without this volume, the Crowdsec container simply won't start
- /home/path/to/crowdsec/config:/etc/crowdsec/ # path to the crowdsec config. This same volume also mounts the acquis.d/ folder - a separate volume for it isn't needed
- /home/path/to/traefik/logs:/var/log/traefik/:ro # specify the paths to the logs crowdsec needs to parse. Note that these are read-only
- /var/log/auth.log:/var/log/auth.log:ro #
- /var/log/syslog:/var/log/syslog:ro #
ports:
- 127.0.0.1:9876:8080 # map the port for local firewall bouncers #
expose: # expose ports for the corresponding tasks
- 8080 # http api for bouncers
- 6060 # metrics endpoint for prometheus
- 7422 # appsec waf endpoint
networks: # specify the network in which both the Traefik reverse proxy and crowdsec run
- proxy #
security_opt: # set permission restrictions
- no-new-privileges:true #
restart: unless-stopped # ensure that if the container crashes, it will always try to restart
networks: # specify that the corresponding network is external and doesn't need to be created
proxy: #
external: true #Setting up log parsing#
Next we need to point Crowdsec at the logs it should be reading - our Linux box’s local logs and the Traefik reverse proxy’s logs - and tell it which port the WAF should listen on.
cscli setup detect command that scans the system on its own and finds installed services (nginx, ssh, various logs, etc.), after which cscli setup install-acquisition generates the acquisition files for you in one go. It mostly works for native (deb/rpm) installs and doesn’t always spot services tucked away at non-standard paths inside containers, so I’m still showing the manual method here - it’s more predictable and easier to follow along with.Log sources used to all get crammed into one acquis.yaml file, separated by --- YAML documents. That still works, but since version 1.5 there’s an acquis.d/ directory (and it’s been the recommended way to do any non-trivial setup ever since) where you can drop one file per source instead. Much easier to work with - no scrolling through a giant file hunting for the right source, and adding or removing one doesn’t touch the rest.
You don’t need a separate volume for acquis.d/ - it’s already there inside the container, since we mounted the whole config folder (/home/path/to/crowdsec/config:/etc/crowdsec/). Just create an acquis.d directory inside it and drop in these files:
acquis.d/traefik.yaml:
filenames:
- /var/log/traefik/*.log
labels:
type: traefikacquis.d/syslog.yaml:
filenames:
- /var/log/syslog
- /var/log/auth.log
labels:
type: syslogacquis.d/appsec.yaml:
listen_addr: 0.0.0.0:7422
appsec_config: crowdsecurity/appsec-default
name: myAppSecComponent
source: appsec
labels:
type: appsecIf you later want to parse logs from some other service - Authentik or Nextcloud, say - just drop in acquis.d/authentik.yaml or acquis.d/nextcloud.yaml, same idea, without touching anything else:
filenames:
- /var/log/authentik.log
labels:
type: authentikCreating an API token for the Bouncer#
Since we’re going to hook up Traefik later, we need a token that lets it talk to the Bouncer.
Generating one is a one-liner:
docker exec crowdsec cscli bouncers add traefik-bouncerCopy that token somewhere safe right away - it’s only shown once, and losing it means starting the whole process over. We’ll plug this token into the middlewares section of Traefik’s dynamic configuration a bit later.
Setting up notifications#
I won’t get into setting up the web panel on the developer’s site in this article. The docs are simple and intuitive - just register and follow the instructions for your OS. It’s hard to mess up, short of doing it on purpose.
What I will cover is getting instant notifications for CrowdSec events, which honestly matters a lot more to me than a pretty dashboard of stats.
I’ll walk through Telegram specifically, but any notification channel works the same way - email, Gotify, Telegram, whatever you prefer.
Full list of supported plugins here.
You only need to touch two files for this.
First, uncomment the two http_default lines in profiles.yaml, located at /path/to/crowdsec/config/profiles.yaml.
It looks something like this:
name: default_ip_remediation
#debug: true
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
- type: ban
duration: 4h
#duration_expr: Sprintf('%dh', (GetDecisionsCount(Alert.GetValue()) + 1) * 4)
notifications:
# - slack_default # Set the webhook in /etc/crowdsec/notifications/slack.yaml before enabling this.
# - splunk_default # Set the splunk url and token in /etc/crowdsec/notifications/splunk.yaml before enabling this.
- http_default # Set the required http parameters in /etc/crowdsec/notifications/http.yaml before enabling this.
# - email_default # Set the required email parameters in /etc/crowdsec/notifications/email.yaml before enabling this.
on_success: break
---
name: default_range_remediation
#debug: true
filters:
- Alert.Remediation == true && Alert.GetScope() == "Range"
decisions:
- type: ban
duration: 4h
#duration_expr: Sprintf('%dh', (GetDecisionsCount(Alert.GetValue()) + 1) * 4)
notifications:
# - slack_default # Set the webhook in /etc/crowdsec/notifications/slack.yaml before enabling this.
# - splunk_default # Set the splunk url and token in /etc/crowdsec/notifications/splunk.yaml before enabling this.
- http_default # Set the required http parameters in /etc/crowdsec/notifications/http.yaml before enabling this.
# - email_default # Set the required email parameters in /etc/crowdsec/notifications/email.yaml before enabling this.
on_success: breakIf you’d rather get notifications by email or another messenger, just uncomment the matching lines instead.
Next, edit notifications/http.yaml, at /path/to/crowdsec/config/notifications.
For Telegram, it looks like this:
type: http # Don't change
name: http_default # Must match the registered plugin in the profile
# One of "trace", "debug", "info", "warn", "error", "off"
log_level: info
# group_wait: # Time to wait collecting alerts before relaying a message to this plugin, eg "30s"
# group_threshold: # Amount of alerts that triggers a message before <group_wait> has expired, eg "10"
# max_retry: # Number of attempts to relay messages to plugins in case of error
# timeout: # Time to wait for response from the plugin before considering the attempt a failure, eg "10s"
#-------------------------
# plugin-specific options
# The following template receives a list of models.Alert objects
# The output goes in the http request body
# Replace XXXXXXXXX with your Telegram chat ID
format: |
{
"chat_id": "-XXXXXXXXX",
"text": "
{{range . -}}
{{$alert := . -}}
{{range .Decisions -}}
{{.Value}} will get {{.Type}} for next {{.Duration}} for triggering {{.Scenario}}.
{{end -}}
{{end -}}
",
"reply_markup": {
"inline_keyboard": [
{{ $arrLength := len . -}}
{{ range $i, $value := . -}}
{{ $V := $value.Source.Value -}}
[
{
"text": "See {{ $V }} on shodan.io",
"url": "https://www.shodan.io/host/{{ $V -}}"
},
{
"text": "See {{ $V }} on crowdsec.net",
"url": "https://app.crowdsec.net/cti/{{ $V -}}"
}
]{{if lt $i ( sub $arrLength 1) }},{{end }}
{{end -}}
]
}
url: https://api.telegram.org/botXXX:YYY/sendMessage # Replace XXX:YYY with your API key
method: POST
headers:
Content-Type: "application/json"Just swap the xxxxxx placeholders for your own values.
For any of this to take effect, restart crowdsec with docker compose up -d --force-recreate.
You can double-check notifications are actually firing:
# list notifications
docker exec crowdsec cscli notifications list
# test a notification channel
docker exec crowdsec cscli notifications test http_defaultThat wraps up the initial Crowdsec setup - on to configuring Traefik.
Configuring the Traefik reverse proxy#
I’ve covered the basics of setting up Traefik in its own article on the site, video included if you want a visual walkthrough.
So from here on, I’ll assume you already know what Traefik is and roughly how it works.
With the Crowdsec container running, log parsers enabled, and an API token for our bouncer in hand, the next step is wiring the bouncer up to Traefik and configuring the right middlewares.
There’s more than one way to add the Crowdsec bouncer to Traefik. The old approach used a forward-auth middleware plus a separate bouncer container - you’ll still run into that method around the web, but it’s outdated at this point. These days it’s all handled through a dedicated Traefik plugin.
First, make sure logging is actually turned on in your Traefik setup.
Configuring Traefik’s logs#
We care specifically about access.log, since that’s what Crowdsec actually reads.
Here’s the relevant snippet from my Traefik article, where I set the static configuration parameters in traefik.yaml.
log: # define the logging level and path for logs
level: "INFO"
filePath: "/var/log/traefik/traefik.log"
accessLog:
filePath: "/var/log/traefik/access.log"but now let’s widen those settings a bit and give Traefik more to work with.
The logging section ends up looking something like this:
log:
level: "INFO"
filePath: "/var/log/traefik/traefik.log"
noColor: false # Recommended to be true when using common. When using the 'common' format, disables the colorized output
maxSize: 100 # In megabytes. Maximum size in megabytes of the log file before it gets rotated.
compress: true # gzip compression when rotating. Determines if the rotated log files should be compressed using gzip.
#
accessLog:
addInternals: true #Enables access logs for internal resources (e.g.: ping@internal)
filePath: "/var/log/traefik/access.log"
bufferingSize: 100 # number of log lines Traefik will keep in memory before writing them to the selected output
fields:
names:
StartUTC: drop # Write logs in Container Local Time instead of UTC. The time at which request processing started.
filters:
statusCodes:
- "204-299"
- "400-599"I want access.log to filter by status code - it speeds things up noticeably. Feel free to tune this however suits your setup.
By the way, having Traefik log in JSON format will speed up Crowdsec’s log reading even further.
A note on Cloudflare#
If your Traefik instance sits behind the CloudFlare CDN, you’ll need to mark Cloudflare’s IPs as trusted in Traefik. Here’s why: requests are effectively passing through two reverse proxies in a row - CloudFlare, then our Traefik - so the visitor’s real IP gets hidden from us, and Traefik only ever sees traffic coming from CloudFlare’s own servers.
Which means Traefik’s logs will only ever show CloudFlare IPs, and that’s useless for CrowdSec, which needs the actual attacker’s IP to do anything about it.
The fix is to mark all of CloudFlare’s IPv4 and IPv6 ranges as trusted. Once Traefik trusts those IPs, it’ll read CloudFlare’s special headers (X-Forwarded-For) to recover the visitor’s real IP address.
There’s more than one way to pull this off. I personally use a Traefik plugin called cloudflarewarp. There are other similar options too, but rather than pile on more complexity in an already dense article, I’ll stick to the more straightforward manual method.
We’ll define CloudFlare’s IPs as trusted directly in Traefik’s entry point definitions.
Open Traefik’s static config file, traefik.yaml, and mark all of CloudFlare’s IPv4 and IPv6 ranges as trusted for your entry points. I’m only showing the part of traefik.yaml that’s relevant here.
Should end up looking something like this:
entryPoints:
http:
address: :80
forwardedHeaders:
trustedIPs: &trustedIps
# Start of Cloudlare's public IP list
- 103.21.244.0/22
- 103.22.200.0/22
- 103.31.4.0/22
- 104.16.0.0/13
- 104.24.0.0/14
- 108.162.192.0/18
- 131.0.72.0/22
- 141.101.64.0/18
- 162.158.0.0/15
- 172.64.0.0/13
- 173.245.48.0/20
- 188.114.96.0/20
- 190.93.240.0/20
- 197.234.240.0/22
- 198.41.128.0/17
- 2400:cb00::/32
- 2606:4700::/32
- 2803:f800::/32
- 2405:b500::/32
- 2405:8100::/32
- 2a06:98c0::/29
- 2c0f:f248::/32
# End of Cloudlare's public IP list
http:
redirections:
entryPoint:
to: https
scheme: https
# HTTPS endpoint, with domain wildcard
https:
address: :443
forwardedHeaders:
# Reuse the list of Cloudflare's public IPs from above
trustedIPs: *trustedIps
http:
tls:
.....
.....Restart Traefik after this and check access.log. You shouldn’t be seeing anonymized CloudFlare IPs in there anymore.
The CrowdSec Bouncer plugin#
First up, install the plugin - its configuration is documented here.
It’s got a ton of configuration options, but I’m only going to cover the one feature that matters most: blocking bad IPs.
Open Traefik’s static config file, traefik.yaml, and add the plugin’s definition:
# crowdsec bouncer
experimental:
plugins:
bouncer: # this is the name we've given the plugin, to link this definition with the values in Traefik's dynamic configuration later
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: v1.7.1 # the current plugin version as of this update, check it against the releases on GitHub before installingRestart Traefik and check traefik.log to confirm the plugin actually loaded.
Wiring up the CrowdSec Bouncer middleware in Traefik#
Downloading and enabling the plugin in Traefik’s static config is only half the job - we still need to wire it up to an actual middleware for it to do anything. Right now it’s basically a car idling in the driveway: the engine’s running, but we’re not going anywhere, just burning fuel (or electrons, if you’re driving an EV).
The middleware definition itself goes in the config.yaml dynamic configuration file (yours may be named differently), where we’ll spell out everything CrowdSec needs. Let’s edit Traefik’s dynamic config and define this middleware:
http:
middlewares:
crowdsec:
plugin:
bouncer:
enabled: true
logLevel: INFO
updateIntervalSeconds: 15
updateMaxFailure: 0
defaultDecisionSeconds: 15
httpTimeoutSeconds: 10
crowdsecMode: stream
crowdsecAppsecEnabled: true
crowdsecAppsecHost: crowdsec:7422
crowdsecAppsecFailureBlock: true
crowdsecAppsecUnreachableBlock: true
crowdsecLapiKey: yourlapikey # Replace CrowdSec API key (docker exec crowdsec cscli bouncers add crowdsecBouncer)
crowdsecLapiHost: crowdsec:8080
crowdsecLapiScheme: http
forwardedHeadersTrustedIPs:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
clientTrustedIPs:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16That’s just a handful of parameters set in the middleware - there are plenty more knobs to turn than what’s shown here. The official docs cover the rest.
Don’t forget to drop your actual API key into the relevant field - the one we generated earlier.
Also worth flagging: forwardedHeadersTrustedIPs and clientTrustedIPs are set to the private IPv4 ranges here. That’s because we need Traefik’s HTTP headers - X-Forwarded-For and X-Real-IP - to be trusted, since those headers carry the real IP of whoever’s visiting your site (attackers included), and CrowdSec relies on them to make its blocking calls.
You could get more surgical and pin this to Traefik’s exact /32 address instead, but whitelisting the whole private-class ranges is just more convenient in practice - especially since Docker’s internal subnet IPs can shift around every time a container restarts.
clientTrustedIPs is a separate thing: it’s the whitelist of IPs or ranges CrowdSec should never block, full stop.
I trust my local LAN, so I’ve whitelisted the private ranges wholesale. You could just as easily scope this down to your specific LAN subnet (CIDR) if you want to be stricter.
Getting Traefik to actually protect something#
Here’s the catch: downloading the plugin and wiring it into a middleware doesn’t protect anything by itself. Traefik knows the plugin exists, and knows it’s capable of protecting things - it just doesn’t yet know which services it should be protecting. Let’s fix that.
Protecting services one at a time with labels#
The straightforward way is flipping on the CrowdSec middleware per-container, using Traefik labels. Here’s what that looks like on the little test container the Traefik team built specifically for this kind of thing.
whoami:
image: traefik/whoami
container_name: whoami-crowdsec-test
command:
- --name=whoami
networks:
- proxy
labels:
- traefik.enable=true
- traefik.docker.network=proxy
- traefik.http.routers.whoami.rule=Host(`whoami.mydomain.ru`)
- traefik.http.routers.whoami.service=whoami
- traefik.http.services.whoami.loadbalancer.server.port=80
** - traefik.http.routers.whoami.middlewares=crowdsec@file**
networks:
proxy:
external: trueThat last label is doing the actual work - it tells Traefik to apply the CrowdSec middleware and points it at the dynamic config file where that middleware is defined.
Or protect everything at the entrypoint level instead#
Personally, I think it makes more sense to protect every service sitting behind Traefik at once, rather than labeling containers one by one.
The upside is obvious: everything’s covered globally, and you can sleep easy without having to remember which service got the label and which didn’t. They’re all protected - every service tucked behind Traefik, no exceptions.
So let’s edit Traefik’s static config file, traefik.yaml, into this shape:
# Traefik entrypoints (network ports) configuration
entryPoints:
http:
address: :80
forwardedHeaders:
trustedIPs: &trustedIps
# Start of Cloudlare's public IP list
- 103.21.244.0/22
- 103.22.200.0/22
- 103.31.4.0/22
- 104.16.0.0/13
- 104.24.0.0/14
- 108.162.192.0/18
- 131.0.72.0/22
- 141.101.64.0/18
- 162.158.0.0/15
- 172.64.0.0/13
- 173.245.48.0/20
- 188.114.96.0/20
- 190.93.240.0/20
- 197.234.240.0/22
- 198.41.128.0/17
- 2400:cb00::/32
- 2606:4700::/32
- 2803:f800::/32
- 2405:b500::/32
- 2405:8100::/32
- 2a06:98c0::/29
- 2c0f:f248::/32
# End of Cloudlare public IP list
http:
redirections:
entryPoint:
to: https
scheme: https
# HTTPS endpoint, with domain wildcard
https:
address: :443
forwardedHeaders:
# Reuse the list of Cloudflare's public IPs from above
trustedIPs: *trustedIps
http:
middlewares:
- crowdsec@file # reference to a dynamic middleware for enabling crowdsec bouncerI applied the middleware on port 443 here, but nothing’s stopping you from doing the same on port 80.
Once that’s done, restart the Traefik container.
Making sure Crowdsec actually works#
We’ve put in a fair amount of setup work by this point, so let’s confirm it all actually holds together - just in case it turns out we did all this for nothing.
Here’s a rundown of the commands you’ll want in your back pocket.
First, let’s check whether Crowdsec can even see Traefik.
This command shows CrowdSec’s metrics, and tells us whether it can see Traefik’s logs and is actually parsing them.
docker exec crowdsec cscli metrics # For Metrics and log read checkingRunning that gives you a wall of tables showing which collections got loaded, but the one at the very top is the one that matters - it tells you whether Crowdsec can actually see the reverse proxy’s access.log. If it can’t, restart Crowdsec first, then Traefik, and check again.
Since version 1.7 there’s also more granular parsing stats - how many lines were read and parsed per log source (parsed, unparsed, whitelisted). Pull that up with:
docker exec crowdsec cscli machines inspect <machine name> # detailed parsing stats per datasource, helps catch a misconfigured acquis.yamlFind your machine’s name with docker exec crowdsec cscli machines list.
This one updates all our collection lists.
docker exec crowdsec cscli hub update && docker exec crowdsec cscli hub upgrade # Security List update commandRather than running this by hand every time, it’s worth setting up a cronjob:
crontab -e, if you’re on a Debian/Ubuntu-based distro.
* * * * * docker exec crowdsec cscli hub update && docker exec crowdsec cscli hub upgradePick whatever interval feels right - any cronjob calculator online will help you get the syntax right.
And now for the part that actually matters: does Crowdsec actually ban bad IPs?
If you work in IT security, faking an attack is trivial. The rest of us mere mortals will do it the simple way instead.
First, comment out the trusted private subnets in the middleware - since we’re testing from our own local network, we want Crowdsec checking every IP with no exceptions for this.
Then manually ban your own work computer’s IP:
docker exec crowdsec cscli decisions add --ip 192.168.x.x # add ip to crowdsec decisions block listNow check the list of banned addresses:
docker exec crowdsec cscli decisions list # List crowdsec ip decisionsYour IP should show up there. Now try opening your Traefik dashboard - but do it in an incognito window.
If everything’s working, you’ll get denied. The default ban lasts 4 hours, but obviously nobody’s waiting that long - let’s just unban ourselves manually.
docker exec crowdsec cscli decisions delete --ip 192.168.x.x # delete ip from crowdsec decisions block listAssuming it all worked (fingers crossed), go ahead and uncomment those private subnets in the middleware again.
And if you just want to browse the full list of alerts your Crowdsec has generated:
docker exec crowdsec cscli alerts list And that’s it, folks.
If you enjoyed this article, you can support me on boosty.




