//------------------------------------------------------------------- //-------------------------------------------------------------------
protect self-hosted apps with CrowdSec

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

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:

mkdir -p ~/crowdsec
cd ~/crowdsec
mkdir -p crowdsec-config/acquis.d
mkdir -p crowdsec-config/parsers/s02-enrich
mkdir -p crowdsec-db
mkdir -p logs/traefik
mkdir -p logs/caddy
mkdir -p dynamic

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

docker network create proxy

After this, your project should look like this:

~/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:

nano ~/crowdsec/docker-compose.yml

Paste this base config:

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:

cd ~/crowdsec
docker compose up -d crowdsec

Check that it is running:

docker compose logs -f crowdsec

You can also check metrics later with:

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:

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

Paste this content into the file:

filenames:
  - /var/log/traefik/access.log
poll_without_inotify: true
labels:
  type: traefik

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

nano ~/crowdsec/docker-compose.yml

You can replace it with this full Traefik stack:

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:

docker exec crowdsec cscli bouncers add traefik-bouncer

Copy the API key output.

Now create the Middleware file:

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

Paste this and replace with your Bouncer Key:

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:

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:

nano ~/myapp/docker-compose.yml

Add labels like this inside the app service:

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:

cd ~/crowdsec
docker 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:

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

Paste:

filenames:
  - /var/log/caddy/access.log
poll_without_inotify: true
labels:
  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.

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:

docker exec crowdsec cscli bouncers add caddy-bouncer

Copy the key.

Use the following command to create the Caddyfile:

nano ~/crowdsec/Caddyfile

Paste this and replace the key and domain:

{
  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:

cd ~/crowdsec
docker 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:

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

Add:

listen_addr: 0.0.0.0:7422
appsec_config: crowdsecurity/appsec-default
name: appsec
source: appsec
labels:
  type: appsec

Then, use the commands below to install AppSec collections:

docker exec crowdsec cscli collections install crowdsecurity/appsec-virtual-patching
docker 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:

nano ~/crowdsec/docker-compose.yml

It should look like this:

expose:
  - "8080"
  - "7422"

Restart CrowdSec:

cd ~/crowdsec
docker compose up -d --force-recreate crowdsec

If you use Traefik, edit:

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

Change it to:

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:

cd ~/crowdsec
docker compose restart traefik

If you use Caddy, edit:

nano ~/crowdsec/Caddyfile

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

{
  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:

cd ~/crowdsec
docker 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:

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

Add:

name: crowdsecurity/whitelists
description: "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:

cd ~/crowdsec
docker 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:

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:

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:

curl ifconfig.me

Add a short test ban:

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:

docker exec crowdsec cscli decisions list

Delete the test ban after that:

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.

FAQs

Do I need CrowdSec if I already use UFW?

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

Do I need both Traefik and Caddy for CrowdSec setup?

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

Can CrowdSec block known bad IPs before they attack me?

Yes. That is one of its biggest benefits.

Post Your Comment

PerLod delivers high-performance hosting with real-time support and unmatched reliability.

Contact us

Payment methods

payment gateway
Perlod Logo
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.