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.
Table of Contents
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.