CrowdSec is an open source intrusion prevention system. Its security engine reads your logs, detects attack patterns such as scans, brute force and exploit probes, and turns them into decisions (for example, "ban this IP for 4 hours"). Separate components called bouncers enforce those decisions. In this tutorial you will set up CrowdSec in front of your web applications on Ubuntu 24.04 in one of two ways: Nginx installed on the host with the Lua-based Nginx bouncer, or Traefik running in Docker with the CrowdSec Traefik plugin.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with a non-root user that has
sudoprivileges. - For Part 1: Nginx installed from the Ubuntu repositories (
sudo apt install nginx) and serving at least one site. - For Part 2: Docker Engine and the Docker Compose plugin installed, and a domain whose DNS A record points to the server if you want Traefik to obtain certificates.
- A second machine with a different public IP to test blocking. Private and loopback addresses are whitelisted by default, so you cannot test a ban against yourself from the server.
Follow Part 1 or Part 2, depending on which reverse proxy you use. The sections on decisions, whitelists and the console apply to both.
How the pieces fit together
| Component | Role |
|---|---|
Security engine (crowdsec) | Reads logs, runs parsers and scenarios, creates alerts and decisions |
| Local API (LAPI) | Stores decisions and serves them to bouncers, on 127.0.0.1:8080 by default |
| Collections | Bundles of parsers and scenarios for a service, such as crowdsecurity/nginx |
| Bouncer | Asks the LAPI for decisions and blocks matching requests |
cscli | Command-line tool to manage everything above |
Besides local detections, the engine downloads a community blocklist of IPs reported by other CrowdSec users, so bouncers also block known attackers before they touch your server.
Part 1: Nginx on the host
Step 1 - Installing CrowdSec
CrowdSec publishes its packages in a packagecloud repository. The vendor provides a script that adds the repository and its signing key. Download it and review it before running it:
curl -fsSL https://install.crowdsec.net -o crowdsec-repo.sh
less crowdsec-repo.sh
sudo sh crowdsec-repo.sh
Install the security engine:
sudo apt install -y crowdsec
During installation CrowdSec detects the services running on the machine. Because Nginx is already installed, it adds the crowdsecurity/nginx collection and configures acquisition of the Nginx logs. Check the service:
sudo systemctl status crowdsec
● crowdsec.service - Crowdsec agent
Loaded: loaded (/usr/lib/systemd/system/crowdsec.service; enabled; preset: enabled)
Active: active (running) since ...
List the installed collections:
sudo cscli collections list
COLLECTIONS
Name Status Version Local Path
crowdsecurity/base-http-scenarios enabled ... /etc/crowdsec/collections/base-http-scenarios.yaml
crowdsecurity/http-cve enabled ... /etc/crowdsec/collections/http-cve.yaml
crowdsecurity/linux enabled ... /etc/crowdsec/collections/linux.yaml
crowdsecurity/nginx enabled ... /etc/crowdsec/collections/nginx.yaml
crowdsecurity/sshd enabled ... /etc/crowdsec/collections/sshd.yaml
If crowdsecurity/nginx is missing, for example because you installed Nginx after CrowdSec, install it and reload the engine:
sudo cscli collections install crowdsecurity/nginx
sudo systemctl reload crowdsec
Step 2 - Checking log acquisition
CrowdSec only sees what it reads. Confirm that the Nginx logs are part of the acquisition configuration:
sudo grep -rA4 nginx /etc/crowdsec/acquis.yaml /etc/crowdsec/acquis.d/ 2>/dev/null
If nothing is returned, or your sites log to a custom path, create a dedicated acquisition file:
sudo nano /etc/crowdsec/acquis.d/nginx.yaml
filenames:
- /var/log/nginx/access.log
- /var/log/nginx/error.log
labels:
type: nginx
Reload CrowdSec and check the acquisition metrics after some traffic has reached the server:
sudo systemctl reload crowdsec
sudo cscli metrics show acquisition
Acquisition Metrics:
Source Lines read Lines parsed Lines unparsed Lines poured to bucket
file:/var/log/nginx/access.log 1.24k 1.24k - 312
Lines parsed close to Lines read means the Nginx parser understands your log format.
Step 3 - Installing the Nginx bouncer
The Nginx bouncer is a Lua module that checks each client IP against the LAPI before Nginx serves the request. The package pulls in the Lua module for Nginx, drops its configuration in /etc/nginx/conf.d/, and registers itself with the local LAPI:
sudo apt install -y crowdsec-nginx-bouncer
Confirm that the bouncer is registered:
sudo cscli bouncers list
Name IP Address Valid Last API pull Type Version
crowdsec-nginx-bouncer-1727260000 127.0.0.1 yes 2026-09-25T10:15:02Z crowdsec-nginx-bouncer v1.x.x
The bouncer settings live in /etc/crowdsec/bouncers/crowdsec-nginx-bouncer.conf. The installer already filled in API_URL and API_KEY. The most useful setting is MODE:
MODE=livequeries the LAPI for each new IP and caches the answer (default).MODE=streamdownloads the full decision list periodically, which is faster on busy servers.
To switch to stream mode, edit the file:
sudo nano /etc/crowdsec/bouncers/crowdsec-nginx-bouncer.conf
MODE=stream
UPDATE_FREQUENCY=10
Test the Nginx configuration and reload it:
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
The bouncer runs inside Nginx, so there is no separate service to enable.
Step 4 - Testing a ban
From your test machine, confirm the site responds normally:
curl -I http://your_server_ip
HTTP/1.1 200 OK
On the server, add a manual decision for the test machine's public IP, replacing your_test_ip:
sudo cscli decisions add --ip your_test_ip --duration 5m --reason "bouncer test"
Repeat the request from the test machine. With MODE=stream, wait up to UPDATE_FREQUENCY seconds first:
curl -I http://your_server_ip
HTTP/1.1 403 Forbidden
Remove the decision when you are done:
sudo cscli decisions delete --ip your_test_ip
Part 2: Traefik in Docker
In this setup CrowdSec runs as a container next to Traefik. Traefik writes its access log to a shared volume, CrowdSec reads it, and the CrowdSec bouncer plugin for Traefik asks the CrowdSec LAPI whether each client IP is allowed.
Step 1 - Creating the project
Create a directory for the stack:
sudo mkdir -p /opt/traefik-crowdsec
cd /opt/traefik-crowdsec
Generate a random key that the bouncer will use to authenticate against the LAPI and store it in an .env file:
echo "CROWDSEC_BOUNCER_KEY=$(openssl rand -hex 32)" | sudo tee .env > /dev/null
sudo chmod 600 .env
Tell CrowdSec where the Traefik logs are:
sudo nano acquis.yaml
filenames:
- /var/log/traefik/access.log
labels:
type: traefik
Step 2 - Writing the Compose file
The CrowdSec image reads two useful environment variables: COLLECTIONS installs hub collections on startup, and BOUNCER_KEY_<name> registers a bouncer with a predefined key.
The Traefik plugin must be pinned to a released version. Look up the latest tag on the plugin's GitHub releases page and put it in place of your_plugin_version (for example v1.4.2).
sudo nano compose.yaml
services:
traefik:
image: traefik:v3.5
restart: unless-stopped
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --accesslog=true
- --accesslog.filepath=/var/log/traefik/access.log
- --experimental.plugins.crowdsec-bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
- --experimental.plugins.crowdsec-bouncer.version=your_plugin_version
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-logs:/var/log/traefik
depends_on:
- crowdsec
crowdsec:
image: crowdsecurity/crowdsec:latest
restart: unless-stopped
environment:
COLLECTIONS: "crowdsecurity/traefik crowdsecurity/http-cve"
BOUNCER_KEY_traefik: ${CROWDSEC_BOUNCER_KEY}
volumes:
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
- crowdsec-db:/var/lib/crowdsec/data
- crowdsec-config:/etc/crowdsec
- traefik-logs:/var/log/traefik:ro
whoami:
image: traefik/whoami
restart: unless-stopped
labels:
- traefik.enable=true
- traefik.http.routers.whoami.rule=PathPrefix(`/`)
- traefik.http.routers.whoami.entrypoints=web
- traefik.http.routers.whoami.middlewares=crowdsec@docker
- traefik.http.middlewares.crowdsec.plugin.crowdsec-bouncer.enabled=true
- traefik.http.middlewares.crowdsec.plugin.crowdsec-bouncer.crowdsecMode=live
- traefik.http.middlewares.crowdsec.plugin.crowdsec-bouncer.crowdsecLapiHost=crowdsec:8080
- traefik.http.middlewares.crowdsec.plugin.crowdsec-bouncer.crowdsecLapiKey=${CROWDSEC_BOUNCER_KEY}
volumes:
traefik-logs:
crowdsec-db:
crowdsec-config:
A few details matter here:
- The middleware is defined once, as
crowdsec, in the labels of thewhoamicontainer. Middlewares declared on any enabled container are available to every router, so other services only need themiddlewares=crowdsec@dockerlabel. crowdsecLapiHostuses the service namecrowdsec, which Docker's internal DNS resolves. The LAPI port is not published on the host.whoamiis a small test application; replace it with your own services.
Step 3 - Starting the stack
Start the containers:
sudo docker compose up -d
Check that all three are running:
sudo docker compose ps
NAME IMAGE STATUS
traefik-crowdsec-crowdsec-1 crowdsecurity/crowdsec:latest Up 40 seconds
traefik-crowdsec-traefik-1 traefik:v3.5 Up 38 seconds
traefik-crowdsec-whoami-1 traefik/whoami Up 40 seconds
Check the Traefik log for plugin errors. A wrong plugin version or module name shows up here:
sudo docker compose logs traefik | grep -i plugin
Confirm that CrowdSec registered the bouncer and parses the Traefik log. Run cscli inside the container:
sudo docker compose exec crowdsec cscli bouncers list
sudo docker compose exec crowdsec cscli metrics show acquisition
The bouncer traefik should be listed as valid, and file:/var/log/traefik/access.log should show parsed lines once some requests have reached the server.
Step 4 - Testing a ban
From your test machine, request the site:
curl -I http://your_server_ip
HTTP/1.1 200 OK
Ban the test machine's IP:
sudo docker compose exec crowdsec cscli decisions add --ip your_test_ip --duration 5m --reason "bouncer test"
Request the site again from the test machine:
curl -I http://your_server_ip
HTTP/1.1 403 Forbidden
Remove the test decision:
sudo docker compose exec crowdsec cscli decisions delete --ip your_test_ip
Importantif Traefik sits behind another proxy or a CDN, the plugin sees the proxy's IP instead of the visitor's. Set
forwardedHeadersTrustedIPson the middleware to the proxy's address ranges so the plugin reads the real client IP fromX-Forwarded-For.
Managing decisions and alerts
The commands below are shown for the host installation. In the Docker setup, prefix them with sudo docker compose exec crowdsec and drop the leading sudo.
List what CrowdSec has detected and what is currently blocked:
sudo cscli alerts list
sudo cscli decisions list
ID Source Scope:Value Reason Action Country AS Events expiration
1043 crowdsec Ip:198.51.100.23 crowdsecurity/http-probing ban NL ... 11 3h58m
Ban a whole range, or lift a ban early:
sudo cscli decisions add --range 198.51.100.0/24 --duration 24h --reason "abusive subnet"
sudo cscli decisions delete --range 198.51.100.0/24
The community blocklist entries appear with origin CAPI and are counted separately. Check them with:
sudo cscli decisions list --origin CAPI | head
Whitelisting trusted IPs
Your uptime monitor, office network or CI runners may trigger scenarios by accident. Whitelist them with a parser in the enrichment stage:
sudo nano /etc/crowdsec/parsers/s02-enrich/my-whitelist.yaml
name: my/whitelist
description: "Trusted monitoring and office addresses"
whitelist:
reason: "trusted source"
ip:
- "203.0.113.5"
cidr:
- "192.0.2.0/24"
Reload CrowdSec and confirm the parser is loaded:
sudo systemctl reload crowdsec
sudo cscli parsers list | grep my/whitelist
In the Docker setup, put the file in the crowdsec-config volume (or mount it into /etc/crowdsec/parsers/s02-enrich/) and restart the container with sudo docker compose restart crowdsec.
Troubleshooting
Requests are never blocked. Check that the bouncer appears as valid in cscli bouncers list with a recent Last API pull. If it does not, the API key or LAPI address is wrong. On the host, create a fresh key with sudo cscli bouncers add nginx-manual, put it in API_KEY in /etc/crowdsec/bouncers/crowdsec-nginx-bouncer.conf and reload Nginx. In Docker, check that .env contains CROWDSEC_BOUNCER_KEY and recreate the stack with sudo docker compose up -d --force-recreate.
The log is read but nothing is parsed. A custom log_format in Nginx can break the parser. Test a real line with cscli explain:
sudo cscli explain --file /var/log/nginx/access.log --type nginx
The output shows each parser stage and where the line stops matching.
You locked yourself out. From the console of your server, delete your own decision with sudo cscli decisions delete --ip your_ip, then add your IP to the whitelist above.
Conclusion
CrowdSec now reads your reverse proxy logs, bans IPs that scan or attack your applications, and blocks them at Nginx or Traefik together with the community blocklist. As next steps, you can install crowdsec-firewall-bouncer-nftables to block the same IPs for every port including SSH, enroll the engine in the CrowdSec console with sudo cscli console enroll your_enrollment_key to see alerts from all your servers in one place, and add the collection for each application you host (for example crowdsecurity/wordpress).
