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 sudo privileges.
  • 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

ComponentRole
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
CollectionsBundles of parsers and scenarios for a service, such as crowdsecurity/nginx
BouncerAsks the LAPI for decisions and blocks matching requests
cscliCommand-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=live queries the LAPI for each new IP and caches the answer (default).
  • MODE=stream downloads 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 the whoami container. Middlewares declared on any enabled container are available to every router, so other services only need the middlewares=crowdsec@docker label.
  • crowdsecLapiHost uses the service name crowdsec, which Docker's internal DNS resolves. The LAPI port is not published on the host.
  • whoami is 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

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).