CrowdSec mit Caddy-Bouncer und AppSec-WAF: Angreifer sperren, eigene Dienste nicht
Teil 17 der Reihe Server im Netz: CrowdSec liest Caddy- und SSH-Logs, der Bouncer sperrt im Webserver, AppSec prüft jede Anfrage. Mit Auswertung echter Logs, gemessener Sperrzeit und einem Fehlalarm, der den eigenen Server aussperrte.
Geprüft am 05.11.2026 · für CrowdSec 1.8.2, caddy-crowdsec-bouncer 0.15.0, Caddy 2.11.7
Mit KI erstellt – redaktionelle Prüfung ausstehend
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Jeder Server im Netz wird ständig abgeklopft: Passwörter für SSH werden durchprobiert, Scanner suchen nach .env-Dateien und bekannten Lücken. CrowdSec liest die Logs mit, erkennt solche Muster und sperrt die Absender. Mit dem Bouncer für Caddy greift die Sperre direkt im Webserver, und die AppSec-Komponente prüft zusätzlich jede Anfrage wie eine Web Application Firewall. Diese Anleitung baut beides auf dem VPS ein, wertet die echten Logs der letzten Tage aus und zeigt zwei Überraschungen: Die Standardregeln stoppen typische Angriffe wie XSS nicht, und das volle Regelwerk sperrte im Test den eigenen Server aus.
Dies ist Teil 17 der Reihe „Server im Netz“. Als Beispiel dient ein netcup-VPS mit Ubuntu und Docker, auf dem Caddy mehrere Dienste unter sedv-lab.de ausliefert, darunter die Website aus Teil 5 und Forgejo aus Teil 15.
Voraussetzungen
- Der VPS aus Teil 4 mit Caddy im Host-Netz und JSON-Logs je Site
- Docker und Docker Compose
- Rund 4 Minuten Zeit für den Bau des eigenen Caddy-Images
- Zugriff auf
/var/log/auth.logfür die SSH-Erkennung
So arbeiten CrowdSec, Bouncer und AppSec
CrowdSec besteht aus drei Teilen. Der Agent liest Logs, zerlegt sie mit Parsern und prüft sie gegen Szenarien wie „viele fehlgeschlagene SSH-Anmeldungen in kurzer Zeit“. Schlägt ein Szenario an, legt die lokale API eine Entscheidung an, meist eine Sperre für vier Stunden. Bouncer holen diese Entscheidungen ab und setzen sie durch, hier der Bouncer im Caddy, der gesperrten Adressen 403 liefert. AppSec ist die dritte Ebene: Caddy schickt jede Anfrage vor der Bearbeitung an CrowdSec, das sie gegen Regeln prüft und bei einem Treffer sofort ablehnen lässt.
Schritt 1: CrowdSec starten
Die lokale API lauscht standardmäßig auf Port 8080. Auf dem Testserver nutzte Caddy diesen Port schon intern für die Website hinter Anubis, deshalb legen wir sie auf 8085. Das geht über eine Datei config.yaml.local, die CrowdSec mit der eigenen Konfiguration zusammenführt:
api:
server:
listen_uri: 127.0.0.1:8085
prometheus:
listen_addr: 127.0.0.1
listen_port: 6062
Welche Logs gelesen werden, steht in Dateien unter acquis.d. Für Caddy, SSH und AppSec:
# acquis.d/caddy.yaml
filenames:
- /var/log/caddy/*.json
labels:
type: caddy
# acquis.d/ssh.yaml
filenames:
- /var/log/host/auth.log
labels:
type: syslog
# acquis.d/appsec.yaml
source: appsec
listen_addr: 127.0.0.1:7422
appsec_configs:
- crowdsecurity/appsec-default
labels:
type: appsec
Die compose.yaml:
services:
crowdsec:
image: crowdsecurity/crowdsec:v1.8.2
container_name: crowdsec
restart: unless-stopped
network_mode: host
environment:
COLLECTIONS: "crowdsecurity/caddy crowdsecurity/sshd crowdsecurity/linux crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules"
LOCAL_API_URL: "http://127.0.0.1:8085"
volumes:
- ./config.yaml.local:/etc/crowdsec/config.yaml.local:ro
- ./acquis:/etc/crowdsec/acquis.d:ro
- ./config:/etc/crowdsec
- ./data:/var/lib/crowdsec/data
- /opt/caddy/log:/var/log/caddy:ro
- /var/log/auth.log:/var/log/host/auth.log:ro
docker compose up -d
docker logs crowdsec 2>&1 | grep -E "Starting tail|Appsec listening"
docker exec crowdsec cscli bouncers add caddy
Der letzte Befehl gibt einen Schlüssel für den Bouncer aus. Er erscheint nur einmal, kommt in eine Datei crowdsec.env neben der Compose-Datei von Caddy als CROWDSEC_API_KEY=… und bekommt chmod 600.
Verifizieren: Das Log zeigt für jede Logdatei Starting tail und Appsec listening on 127.0.0.1:7422, ss -tlnp die Ports 8085, 7422 und 6062 nur auf 127.0.0.1.
Schritt 2: Was in den Logs steckt
CrowdSec kann vorhandene Logs nachträglich durchgehen. So sieht man vor dem Scharfschalten, was es erkannt hätte:
docker exec crowdsec crowdsec -c /etc/crowdsec/config.yaml \
-dsn file:///var/log/host/auth.log -type syslog -no-api | grep performed

Auf dem SSH-Port lag ssh-slow-bf mit 350 Erkennungen vorn. Das ist die langsamere Stufe zu ssh-bf: Sie schlägt an, wenn mehr als zehn Fehlversuche anfallen, auch wenn sie sich über Minuten verteilen. Eine einzelne Adresse kam auf 227 Erkennungen, weil in der Auswertung niemand sperrt und sie immer weiter probierte. Bei HTTP fanden sich gezielte Versuche gegen bekannte Lücken, etwa jira_cve-2021-26086 und netgear_rce, dazu die Suche nach sensiblen Dateien und nach Admin-Oberflächen. Keine dieser Anwendungen läuft auf dem Server, die Scanner probieren blind. Ein Teil der HTTP-Treffer stammt aus der Falle für Aufrufe per IP-Adresse aus Teil 1. Für die Caddy-Logs lautet der Aufruf -dsn file:///var/log/caddy/sedv-lab.json -type caddy.
Verifizieren: Jede Zeile der Ausgabe nennt Adresse, Szenario und Zeitpunkt, etwa Ip … performed 'crowdsecurity/http-sensitive-files'.
Schritt 3: Caddy mit Bouncer bauen
Der Bouncer ist ein Modul für Caddy, das offizielle Image enthält ihn nicht. Mit xcaddy entsteht ein eigenes Image:
FROM caddy:2.11.7-builder AS builder
RUN xcaddy build \
--with github.com/hslatman/caddy-crowdsec-bouncer/http@v0.15.0 \
--with github.com/hslatman/caddy-crowdsec-bouncer/appsec@v0.15.0
FROM caddy:2.11.7
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
docker build -t caddy-crowdsec:2.11.7 .
Der Bau dauerte auf dem VPS 211 Sekunden.
Verifizieren: docker run --rm caddy-crowdsec:2.11.7 caddy list-modules | grep -iE "crowdsec|appsec" zeigt unter anderem http.handlers.crowdsec und http.handlers.appsec.
In der Compose-Datei von Caddy ersetzen Sie das Image und laden den Schlüssel:
image: caddy-crowdsec:2.11.7
env_file: ./crowdsec.env
Wichtig für später: Das eigene Image wird nicht mit Caddy-Updates mitgeliefert. Bei jedem neuen Caddy-Release bauen Sie es neu.
Schritt 4: Caddyfile erweitern
Im globalen Block kommen die Verbindung zu CrowdSec und die Reihenfolge der neuen Direktiven dazu, danach ein Snippet für alle geschützten Sites:
{
email admin@sedv-lab.de
order crowdsec first
order appsec after crowdsec
crowdsec {
api_url http://127.0.0.1:8085
api_key {$CROWDSEC_API_KEY}
ticker_interval 15s
appsec_url http://127.0.0.1:7422
}
}
(schutz) {
crowdsec
appsec
}
sedv-lab.de {
import sicher
import schutz
...
}
Für Dienste mit Uploads oder eigenen Protokollen wie den REST-Server aus Teil 16 reicht crowdsec allein, dort prüft AppSec nicht mit.
docker run --rm --env-file crowdsec.env -v "$PWD/Caddyfile:/etc/caddy/Caddyfile:ro" \
caddy-crowdsec:2.11.7 caddy validate --config /etc/caddy/Caddyfile
docker compose up -d
Der Wechsel auf das neue Image ist ein Neustart: Im Test waren die Sites nach 11,4 Sekunden wieder erreichbar.
Verifizieren: Im Log von Caddy stehen appsec enabled und decision store populated, docker exec crowdsec cscli bouncers list zeigt den Bouncer mit Version v0.15.0. Im Test übernahm Caddy gleich 17 Sperren aus den laufenden Logs.
Schritt 5: Sperren testen
Eine Sperre lässt sich von Hand anlegen, etwa für die eigene Adresse für drei Minuten:
docker exec crowdsec cscli decisions add --ip EIGENE-IP --duration 3m --reason test
docker exec crowdsec cscli decisions delete --ip EIGENE-IP
Nach 14 Sekunden bekam die Adresse auf allen Sites 403, das passt zu ticker_interval 15s. Nach dem Löschen war sie in unter 3 Sekunden wieder frei.
Verifizieren: curl -s -o /dev/null -w '%{http_code}\n' https://sedv-lab.de/ liefert während der Sperre 403.
Schritt 6: Die WAF und ihre Grenzen
Mit den Standardregeln haben wir typische Angriffe geschickt:

appsec-default enthält virtuelle Patches für bekannte Lücken und Regeln gegen offensichtliche Zugriffe. /.env, /.git/config, die Jira-Lücke CVE-2021-26086 und die PHPUnit-Lücke CVE-2017-9841 wurden mit 403 abgewiesen. Ein klassisches XSS im Parameter, eine SQL-Injection und ein Pfad mit ../ kamen dagegen mit 200 durch. Wer diese Fälle abfangen will, braucht das OWASP Core Rule Set:
docker exec crowdsec cscli collections install crowdsecurity/appsec-crs-inband
Dazu kommt crowdsecurity/crs-inband unter appsec_configs in der appsec.yaml, dann ein Neustart von CrowdSec. Jetzt endeten alle drei Angriffe mit 403, normale Seiten liefen weiter.
Dann kam die Überraschung. Der Runner von Forgejo aus Teil 15 fragt seine Aufträge per POST mit einem Inhaltstyp ab, den das Regelwerk nicht kennt. Regel 920420 meldete Request content type is not allowed by policy, das Szenario appsec-native sperrte die IPv6-Adresse des Servers selbst, und ein git push endete mit The requested URL returned error: 403. Schon vorher hatten unsere eigenen Proben mit dem Szenario appsec-vpatch die Adresse des Testrechners für vier Stunden gesperrt.
Zwei Maßnahmen lösen das. Erstens bekommt Forgejo nur crowdsec ohne appsec, denn einzelne Anfragen kann AppSec trotz Freiliste abweisen. Zweitens verhindert eine Freiliste in config/parsers/s02-enrich/eigene-ips.yaml, dass die eigenen Adressen gesperrt werden. Für längere Listen empfiehlt die Doku die zentralen Allowlists mit cscli allowlists.
name: sedv/eigene-ips
description: "Eigene Server-Adressen nie sperren"
whitelist:
reason: "eigener Server"
ip:
- "203.0.113.10"
- "2001:db8:1:2::10"
- "127.0.0.1"
- "::1"
Nach einem Neustart von CrowdSec und dem Löschen der Sperre lief der git push wieder, und der Workflow aus Teil 15 rollte die Seite aus.
Verifizieren: docker exec crowdsec cscli parsers list zeigt sedv/eigene-ips als enabled,local, cscli decisions list enthält keine eigenen Adressen.
Typische Fehler
| Meldung oder Beobachtung | Ursache und Lösung |
|---|---|
cscli bouncers list ist leer | Bouncer nicht angelegt. Mit cscli bouncers add caddy anlegen und Schlüssel in Caddy eintragen. |
| Port 8080 belegt | Lokale API über config.yaml.local auf einen anderen Port legen, in Caddy api_url anpassen. |
| Eigener Dienst bekommt plötzlich 403 | AppSec-Fehlalarm und Sperre. cscli alerts inspect ID -d zeigt Regel und Adresse, Site ohne appsec betreiben oder Ausnahme anlegen, Freiliste für eigene Adressen. |
| XSS und SQL-Injection kommen durch | Nur appsec-default aktiv. Core Rule Set nachinstallieren, Fehlalarme prüfen. |
Im Caddy-Log appsec component unavailable … connection refused | CrowdSec startet gerade neu. Laut README steht appsec_fail_open auf false, Anfragen an Sites mit appsec können dann scheitern. Neustarts kurz halten oder die Option bewusst setzen. |
| Nach Caddy-Update fehlt der Bouncer | Offizielles Image ohne Modul. Eigenes Image mit xcaddy neu bauen. |
Häufige Fragen
Was bringt das gegen SSH-Angriffe?
CrowdSec erkennt sie, der Bouncer im Caddy sperrt aber nur Webzugriffe. Damit die Sperre auch SSH erfasst, braucht es einen Bouncer für die Firewall. Den Schutz von SSH selbst behandelt Teil 2.
Teilt CrowdSec meine Daten?
Ja, standardmäßig. Laut Doku sendet die Installation zu jeder Erkennung Szenario, Zeitpunkt, Maschinen-ID sowie IP und Herkunft des Angreifers an die Central API und bekommt im Gegenzug Sperrlisten. Wie man das abschaltet, beschreibt die Doku unter dem Stichwort Central API.
Kostet AppSec Leistung?
Jede Anfrage geht zusätzlich an CrowdSec. Für eine kleine Website fällt das kaum ins Gewicht. Bei großen Uploads lohnt es sich, AppSec für diese Site wegzulassen, wie beim REST-Server.
Fazit
CrowdSec macht sichtbar, was ständig auf einen Server einprasselt, und sperrt die Absender im Webserver binnen Sekunden. Der Bouncer für Caddy braucht ein eigenes Image, das sich in vier Minuten bauen lässt. Bei der WAF lohnt der genaue Blick: Die Standardregeln fangen bekannte Lücken ab, aber keine generischen Angriffe. Das Core Rule Set schließt die Lücke, braucht aber eine Freiliste und Ausnahmen, sonst sperrt es eigene Dienste aus.
Weiterführende Anleitungen und Quellen
- Teil 4: Caddy als Eingangstür
- Teil 6: Anubis vor der Website
- Teil 15: Forgejo mit Actions-Runner
- Teil 16: Offsite-Backup mit restic
- Linux-Server absichern: UFW und Fail2ban
- Docker und Docker Compose auf Linux installieren
- Docker Compose absichern
- CrowdSec-Bouncer für Caddy
- CrowdSec: AppSec
- CrowdSec: Freilisten
- CrowdSec Hub: Sammlungen


