How to Secure Self-Hosted Apps with CrowdSec and Traefik or Caddy

Updated on Aug 24, 2026
Kimberly N
10 MINS READ
Table of Contents
protect self-hosted apps with CrowdSec

Running your own apps on a VPS puts you on the open internet, where bots and bad IPs try to break in all day. A regular firewall just opens or closes ports; it can't tell if traffic on port 443 is a real visitor or an attack. That's where protect self-hosted apps with CrowdSec comes in. It sits between your reverse proxy and the outside world, reads behavior patterns, and automatically bans offenders in real time.

This guide teaches you to deploy CrowdSec with Docker Compose, connect it to both Traefik and Caddy using the right bouncer for each.

What Is CrowdSec and Why Do You Need It?

CrowdSec is a free, open-source security tool that watches your logs and blocks attacks. Unlike a regular firewall, which only follows fixed rules, CrowdSec learns attack patterns and shares them with a global community. So if one server spots a bad IP, every other CrowdSec user gets protected from it.

It works in two parts:

  • Security Engine (Agent): Reads logs, detects threats, and makes decisions.
  • Remediation Component (Bouncer): Enforces those decisions at the entry point, in this case, inside Traefik or Caddy.

If you run a Linux VPS Server with apps exposed to the internet, pairing Traefik or Caddy with CrowdSec is one of the best security setups you can use.

Prerequisites to Protect Self-Hosted Apps with CrowdSec

Before starting to protect self-hosted apps with CrowdSec, make sure you have:

  • A Linux VPS running Ubuntu 24.04.
  • Docker and Docker Compose are installed.
  • Either Traefik or Caddy is already planned as your reverse proxy.
  • Root or Sudo access to the server.

If you need to get Traefik running, you can check this guide on Setting up Traefik with Docker Compose.

If you need Caddy as a reverse proxy, check this guide on Deploying a Reverse Proxy with Caddy.

Folder layout of CrowdSec Setup

First, you must create the project folder. To do this, run the commands below:

Bash
mkdir -p ~/crowdseccd ~/crowdsecmkdir -p crowdsec-config/acquis.dmkdir -p crowdsec-config/parsers/s02-enrichmkdir -p crowdsec-dbmkdir -p logs/traefikmkdir -p logs/caddymkdir -p dynamic

Create the shared Docker network if you do not already have one:

Bash
docker network create proxy

After this, your project should look like this:

Bash
~/crowdsec/├── crowdsec-config/│   ├── acquis.d/│   └── parsers/│       └── s02-enrich/├── crowdsec-db/├── dynamic/├── logs/│   ├── caddy/│   └── traefik/└── docker-compose.yml

Part 1. Deploy CrowdSec with Docker Compose

This is the core engine to which everything else connects. To create the base CrowdSec Docker Compose YAML file, use the command below:

Bash
nano ~/crowdsec/docker-compose.yml

Paste this base config:

YAML
services:  crowdsec:    image: crowdsecurity/crowdsec:latest    container_name: crowdsec    restart: unless-stopped    environment:      COLLECTIONS: "crowdsecurity/linux crowdsecurity/http-cve crowdsecurity/http-dos"    expose:      - "8080"    volumes:      - ./crowdsec-config:/etc/crowdsec      - ./crowdsec-db:/var/lib/crowdsec/data      - /var/log/auth.log:/var/log/auth.log:ro    networks:      - proxy networks:  proxy:    external: true

Save and exit.

Once you are done, start CrowdSec:

Bash
cd ~/crowdsecdocker compose up -d crowdsec

Check that it is running:

Bash
docker compose logs -f crowdsec

You can also check metrics later with:

Bash
docker exec crowdsec cscli metrics

At this point, protect self-hosted apps with CrowdSec is not complete yet because CrowdSec still needs logs from Traefik or Caddy.

Part 2. Protect Self-Hosted Apps with CrowdSec Using Traefik

This section covers integrating CrowdSec with Traefik using the official Traefik plugin. Use this if Traefik is your reverse proxy.

Important Note: Use the maxlerebourg/crowdsec-bouncer-traefik-plugin, not the older fbonalair/traefik-crowdsec-bouncer. The older one does not support the AppSec component and is no longer actively maintained.

Create Traefik YAML File

This file tells CrowdSec to read Traefik access logs. Create the file with the command below:

Bash
nano ~/crowdsec/crowdsec-config/acquis.d/traefik.yaml

Paste this content into the file:

YAML
filenames:  - /var/log/traefik/access.logpoll_without_inotify: truelabels:  type: traefik

Now update the CrowdSec Compose file so it includes Traefik too.

Bash
nano ~/crowdsec/docker-compose.yml

You can replace it with this full Traefik stack:

YAML
services:  crowdsec:    image: crowdsecurity/crowdsec:latest    container_name: crowdsec    restart: unless-stopped    environment:      COLLECTIONS: "crowdsecurity/linux crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/http-dos"    expose:      - "8080"    volumes:      - ./crowdsec-config:/etc/crowdsec      - ./crowdsec-db:/var/lib/crowdsec/data      - ./logs/traefik:/var/log/traefik:ro      - /var/log/auth.log:/var/log/auth.log:ro    networks:      - proxy   traefik:    image: traefik:latest    container_name: traefik    restart: unless-stopped    command:      - --providers.docker=true      - --providers.docker.exposedbydefault=false      - --providers.file.directory=/dynamic      - --providers.file.watch=true      - --entrypoints.web.address=:80      - --entrypoints.websecure.address=:443      - --api.dashboard=true      - --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=v1.3.0    ports:      - "80:80"      - "443:443"    volumes:      - /var/run/docker.sock:/var/run/docker.sock:ro      - ./dynamic:/dynamic      - ./logs/traefik:/var/log/traefik    networks:      - proxy    depends_on:      - crowdsec networks:  proxy:    external: true

Define the Middleware

You can define the CrowdSec middleware as Docker labels on Traefik itself, or as a dynamic config file. The label method is the most common for Docker-based setups.

First, create the bouncer key:

Bash
docker exec crowdsec cscli bouncers add traefik-bouncer

Copy the API key output.

Now create the Middleware file:

Bash
nano ~/crowdsec/dynamic/crowdsec-middleware.yml

Paste this and replace with your Bouncer Key:

YAML
http:  middlewares:    crowdsec-bouncer:      plugin:        crowdsec-bouncer:          enabled: true          crowdsecLapiKey: PASTE_YOUR_KEY_HERE          crowdsecLapiHost: crowdsec:8080          defaultDecisionSeconds: 60

If you put Cloudflare or another proxy in front of Traefik, add forwardedHeadersTrustedIPs to the middleware config, so CrowdSec sees the real visitor IP instead of the proxy's IP:

YAML
http:  middlewares:    crowdsec-bouncer:      plugin:        crowdsec-bouncer:          enabled: true          crowdsecLapiKey: PASTE_YOUR_KEY_HERE          crowdsecLapiHost: crowdsec:8080          defaultDecisionSeconds: 60          forwardedHeadersTrustedIPs:            - "173.245.48.0/20"

Apply the Middleware to Your App

For every service you want to protect, add the middleware to its router labels. For example, if your app is in ~/myapp/docker-compose.yml, open that file:

Bash
nano ~/myapp/docker-compose.yml

Add labels like this inside the app service:

YAML
labels:  - "traefik.enable=true"  - "traefik.http.routers.myapp.rule=Host(`app.example.com`)"  - "traefik.http.routers.myapp.entrypoints=websecure"  - "traefik.http.routers.myapp.tls=true"  - "traefik.http.routers.myapp.middlewares=crowdsec-bouncer@file"networks:  - proxy networks:  proxy:    external: true

Note: Make sure the app is also attached to the same proxy network as Traefik; routing will not work.

Save the file and restart the app. Use the command below to start the Traefik stack:

Bash
cd ~/crowdsecdocker compose up -d

Then restart your protected app stack if needed.

At this point, protect self-hosted apps with CrowdSec is active for any Traefik router that uses crowdsec-bouncer@file.

Part 3. Protect Self-Hosted Apps with CrowdSec Using Caddy

Use this section only if you want Caddy instead of Traefik.

Caddy requires a custom build that includes the CrowdSec bouncer module, because Caddy does not support runtime plugins as Traefik does.

Use the command below to create the Caddy YAML file:

Bash
nano ~/crowdsec/crowdsec-config/acquis.d/caddy.yaml

Paste:

YAML
filenames:  - /var/log/caddy/access.logpoll_without_inotify: truelabels:  type: caddy

Then, update the CrowdSec Compose file with this Caddy config:

Note: ghcr.io/buildplan/cs-caddy is a community-maintained image, not an official Caddy or CrowdSec build. If you prefer an official path, build your own image with xcaddy and the github.com/hslatman/caddy-crowdsec-bouncer module instead.

YAML
services:  crowdsec:    image: crowdsecurity/crowdsec:latest    container_name: crowdsec    restart: unless-stopped    environment:      COLLECTIONS: "crowdsecurity/linux crowdsecurity/caddy crowdsecurity/http-cve crowdsecurity/http-dos"    expose:      - "8080"    volumes:      - ./crowdsec-config:/etc/crowdsec      - ./crowdsec-db:/var/lib/crowdsec/data      - ./logs/caddy:/var/log/caddy:ro      - /var/log/auth.log:/var/log/auth.log:ro    networks:      - proxy   caddy:    image: ghcr.io/buildplan/cs-caddy:latest    container_name: caddy    restart: unless-stopped    ports:      - "80:80"      - "443:443"    volumes:      - ./Caddyfile:/etc/caddy/Caddyfile      - ./logs/caddy:/var/log/caddy      - ./caddy_data:/data      - ./caddy_config:/config    networks:      - proxy    depends_on:      - crowdsec networks:  proxy:    external: true

Now you must create the bouncer key:

Bash
docker exec crowdsec cscli bouncers add caddy-bouncer

Copy the key.

Use the following command to create the Caddyfile:

Bash
nano ~/crowdsec/Caddyfile

Paste this and replace the key and domain:

Bash
{  log {    output file /var/log/caddy/access.log    format json  }   crowdsec {    api_url http://crowdsec:8080    api_key PASTE_YOUR_KEY_HERE    ticker_interval 15s  }} app.example.com {  log  route {    crowdsec    reverse_proxy app:3000  }}

Once you are done, start the Caddy stack:

Bash
cd ~/crowdsecdocker compose up -d

Now protect self-hosted apps with CrowdSec is active for that Caddy site block.

Part 4. Add AppSec Firewall Layer

AppSec is CrowdSec's web application firewall layer. It can help block exploit payloads and bad requests. To create the file, you can use the command below:

Bash
nano ~/crowdsec/crowdsec-config/acquis.d/appsec.yaml

Add:

YAML
listen_addr: 0.0.0.0:7422appsec_config: crowdsecurity/appsec-defaultname: appsecsource: appseclabels:  type: appsec

Then, use the commands below to install AppSec collections:

Bash
docker exec crowdsec cscli collections install crowdsecurity/appsec-virtual-patchingdocker exec crowdsec cscli collections install crowdsecurity/appsec-generic-rules

Update Files for AppSec

In the CrowdSec service, you must add port 7422 to expose:. Edit the file:

Bash
nano ~/crowdsec/docker-compose.yml

It should look like this:

YAML
expose:  - "8080"  - "7422"

Restart CrowdSec:

Bash
cd ~/crowdsecdocker compose up -d --force-recreate crowdsec

If you use Traefik, edit:

Bash
nano ~/crowdsec/dynamic/crowdsec-middleware.yml

Change it to:

YAML
http:  middlewares:    crowdsec-bouncer:      plugin:        crowdsec-bouncer:          enabled: true          crowdsecLapiKey: PASTE_YOUR_KEY_HERE          crowdsecLapiHost: crowdsec:8080          crowdsecAppsecEnabled: true          crowdsecAppsecHost: crowdsec:7422          crowdsecAppsecFailureBlock: true

Restart Traefik:

Bash
cd ~/crowdsecdocker compose restart traefik

If you use Caddy, edit:

Bash
nano ~/crowdsec/Caddyfile

Add appsec_url in the global crowdsec block, then add appsec inside the route.

Bash
{  log {    output file /var/log/caddy/access.log    format json  }   crowdsec {    api_url http://crowdsec:8080    api_key PASTE_YOUR_KEY_HERE    ticker_interval 15s    appsec_url http://crowdsec:7422  }} app.example.com {  log  route {    crowdsec    appsec    reverse_proxy app:3000  }}

Restart Caddy:

Bash
cd ~/crowdsecdocker compose restart caddy

This step makes protect self-hosted apps with CrowdSec stronger because requests are checked by IP reputation and by request content.

Part 5. Create a CrowdSec Whitelist

This is a safe default for single-server setups. It helps prevent internal or private IPs from being blocked.

Use the command below to create the whitelist file:

Bash
nano ~/crowdsec/crowdsec-config/parsers/s02-enrich/whitelists.yaml

Add:

YAML
name: crowdsecurity/whitelistsdescription: "Whitelist local and private IPs"whitelist:  reason: "private ranges"  ip:    - "127.0.0.1"    - "::1"  cidr:    - "10.0.0.0/8"    - "172.16.0.0/12"    - "192.168.0.0/16"

Restart CrowdSec:

Bash
cd ~/crowdsecdocker compose restart crowdsec

This is one of the best small changes you can make when you protect self-hosted apps with CrowdSec on one VPS.

Part 6. Verify Everything Works Correctly with CrowdSec

It is recommended to check everything after your setup.

First, check that logs are being read:

Bash
docker exec crowdsec cscli metrics

Look for acquisition metrics. You should see lines for Traefik or Caddy logs.

Then, check if the bouncer is connected:

Bash
docker exec crowdsec cscli bouncers list

You should see traefik-bouncer or caddy-bouncer with a recent last pull time.

Now you can manually ban your IP. Find your public IP:

Bash
curl ifconfig.me

Add a short test ban:

Bash
docker exec crowdsec cscli decisions add --ip YOUR.PUBLIC.IP --duration 5m

Open your app in the browser. You should get blocked.

Check active bans:

Bash
docker exec crowdsec cscli decisions list

Delete the test ban after that:

Bash
docker exec crowdsec cscli decisions delete --ip YOUR.PUBLIC.IP

When you protect self-hosted apps with CrowdSec, this quick test is the easiest way to prove the setup really works.

When CrowdSec Helps More Than a Firewall

A basic firewall is still important, but it cannot read web logs. CrowdSec helps more in cases like these:

  • Login brute-force attacks against Vaultwarden, Nextcloud, WordPress, or Authentik.
  • Bots scanning for .env, /wp-login.php, or old CVE paths.
  • Repeat bad traffic coming from IPs already known to the CrowdSec community.
  • Basic request floods that should be blocked before they waste app resources.

If you also use a login gateway, installing Authentik with Docker Compose is a good next step after you protect self-hosted apps with CrowdSec.

Conclusion

Traefik and Caddy are great at routing traffic and handling HTTPS, but they do not act like a smart security layer by themselves. CrowdSec adds that missing layer by reading logs, detecting attacks, and blocking bad traffic before it reaches your apps.

If you want a simple and practical way to protect self-hosted apps with CrowdSec, this setup is one of the best options for a single VPS. It is lightweight, works well with Docker, and is easy to test once the files are in the right place.

Harden your exposed apps on a PerLod Linux VPS before brute-force and bot traffic become real incidents.

We hope you enjoy this guide. For more information, you can check the CrowdSec official docs.

Yes. UFW filters ports. CrowdSec reads app and proxy logs.

No. Pick one reverse proxy and connect CrowdSec to that one.

Yes. That is one of its biggest benefits.