Zum Inhalt springen
Serverküche
Suche

Die Suche wird geladen … (nur in der veröffentlichten Seite verfügbar).

Sicherheit Schwierigkeit: Profi

CrowdSec: moderne, kollaborative Angriffsabwehr

CrowdSec mit dem Traefik-Bouncer einrichten: Angriffe aus den Zugriffs-Logs erkennen, Angreifer per 403 blocken und von der Community-Blocklist profitieren.

· 13 Min. Lesezeit ·Dauer: ca. 45 Minuten
Inhaltsverzeichnis

Fail2ban blockt Brute-Force auf deinen SSH-Zugang – aber was ist mit den Scannern, die den ganzen Tag deine Web-Apps nach Lücken abklopfen? CrowdSec schließt genau diese Lücke: Es erkennt Angriffe in den Zugriffs-Logs von Traefik und sperrt die Angreifer, bevor sie deine Anwendungen überhaupt erreichen.

Was bauen wir?

Am Ende läuft ein CrowdSec-Agent neben deinem Traefik, der die Zugriffs-Logs mitliest, verdächtiges Verhalten anhand von Szenarien erkennt (Port-Scans, Path-Probing, bekannte CVE-Ausnutzung, Login-Brute-Force) und Angreifer-IPs auf eine Sperrliste setzt. Ein Bouncer – hier als Traefik-Plugin – setzt diese Entscheidungen durch: Eine gesperrte IP bekommt bei jeder deiner Apps nur noch 403 Forbidden. Getestet mit CrowdSec v1.7.8, dem Traefik-Bouncer-Plugin v1.6.0 und Traefik v3.7.

So greifen die Teile ineinander:

  • Agent + Local API (LAPI): liest die Logs, wertet sie gegen die Szenarien aus und verwaltet die aktiven Sperren („Decisions"). Das ist der Container, den wir aufsetzen.
  • Bouncer: fragt die LAPI bei jeder Anfrage „ist diese IP gesperrt?" und blockt bei Ja. Der Traefik-Bouncer läuft als Plugin direkt im Reverse Proxy – kein zusätzlicher Container.
  • Community-Blocklist: CrowdSec meldet (anonymisiert) erkannte Angriffe an das zentrale Netzwerk und bekommt dafür eine von allen Nutzern gespeiste Blocklist zurück. IPs, die andernorts schon als bösartig auffielen, sind bei dir dann von vornherein gesperrt. Genau das ist das „kollaborativ" im Titel.

CrowdSec statt fail2ban – oder zusätzlich?

CrowdSec ersetzt fail2ban nicht, es ergänzt es. fail2ban ist unschlagbar einfach für einen Dienst auf dem Host (SSH). CrowdSec spielt seine Stärke bei HTTP-Apps hinter dem Reverse Proxy aus: ein Bouncer schützt alle Apps auf einmal, die Szenarien sind gepflegt und aktuell, und die Community-Blocklist bekommst du gratis dazu. Die saubere Aufteilung: fail2ban bleibt auf SSH, CrowdSec übernimmt die Web-Ebene. Beide dürfen parallel laufen.

Voraussetzungen

  • Ein laufender Reverse Proxy mit Traefik (das proxy-Netz und der Resolver le stammen von dort) mit mindestens einer App dahinter.
  • Fail2ban für den SSH-Schutz – CrowdSec kümmert sich hier um die HTTP-Ebene, nicht um SSH.
  • Grundwissen zu Docker Compose und ein abgesicherter Server mit aktiver Firewall.
🍳 Empfehlung Anzeige

VPS 2000 G12

8 vCore · 16 GB RAM · 512 GB NVMe

ab 19,24 €/Monat

CrowdSec läuft mit wenigen hundert MB neben deinem bestehenden App-Stack – der VPS 2000 hat dafür genug Reserve.

Zu netcup →

💶 5 € Gutschein für netcup-Neukunden: 36nc17844976032 (nur Neukunden, keine Domains)

Schritt für Schritt

Schritt 1: Traefiks Zugriffs-Logs einschalten

CrowdSec kann nur erkennen, was es sieht – und es sieht Angriffe über Traefiks Zugriffs-Logs. Die sind standardmäßig aus. Öffne die compose.yaml deines Traefik (aus dem Traefik-Tutorial, Ordner ~/traefik) und ergänze im command-Block diese drei Zeilen:

YAML
      - "--accesslog=true"
      - "--accesslog.filepath=/var/log/traefik/access.log"
      - "--accesslog.format=json"

Das JSON-Format ist wichtig – die CrowdSec-Parser erwarten es so. Damit sowohl Traefik (schreibend) als auch CrowdSec (lesend) an dieselbe Datei kommen, legst du sie auf einen festen Host-Pfad. Ergänze im volumes-Block von Traefik:

YAML
      - /var/log/traefik:/var/log/traefik

Lege das Verzeichnis auf dem Host an und starte Traefik neu:

Terminal
sudo mkdir -p /var/log/traefik
cd ~/traefik && docker compose up -d

Ruf einmal eine deiner Apps auf und prüfe, dass Zeilen ankommen:

Terminal
tail -n 2 /var/log/traefik/access.log

Du solltest JSON-Zeilen mit "ClientHost", "RequestPath" und "DownstreamStatus" sehen. Bleibt die Datei leer, stimmt der Pfad im Volume nicht, oder es kam schlicht noch keine Anfrage.

Schritt 2: Den CrowdSec-Container aufsetzen

CrowdSec bekommt einen eigenen Ordner. Erzeuge zuerst einen Bouncer-Schlüssel – mit ihm authentifiziert sich später der Traefik-Bouncer an der LAPI:

Terminal
mkdir -p ~/crowdsec && cd ~/crowdsec
openssl rand -hex 32

Kopiere die Ausgabe; sie kommt gleich an zwei Stellen zum Einsatz. Lege die Acquisition an – sie sagt CrowdSec, welche Datei mit welchem Log-Typ zu lesen ist:

YAML
# ~/crowdsec/acquis.yaml
filenames:
  - /var/log/traefik/access.log
labels:
  type: traefik

Jetzt die compose.yaml. Ersetze DEIN_BOUNCER_KEY durch den erzeugten Schlüssel:

YAML
services:
  crowdsec:
    image: crowdsecurity/crowdsec:v1.7.8
    container_name: crowdsec
    environment:
      COLLECTIONS: "crowdsecurity/traefik"
      BOUNCER_KEY_TRAEFIK: "DEIN_BOUNCER_KEY"
      GID: "1000"
    volumes:
      - ./acquis.yaml:/etc/crowdsec/acquis.d/traefik.yaml:ro
      - /var/log/traefik:/var/log/traefik:ro
      - crowdsec-db:/var/lib/crowdsec/data/
      - crowdsec-config:/etc/crowdsec/
    networks: [proxy]
    restart: unless-stopped

volumes:
  crowdsec-db:
  crowdsec-config:

networks:
  proxy:
    external: true

Was hier wichtig ist:

  • COLLECTIONS: crowdsecurity/traefik installiert beim ersten Start die passende Sammlung aus Parsern und Szenarien für Traefik-Logs (inklusive der HTTP-Basisszenarien).
  • BOUNCER_KEY_TRAEFIK registriert direkt beim Start einen Bouncer namens TRAEFIK mit deinem Schlüssel – das spart den Weg über cscli bouncers add.
  • GID: "1000" lässt den CrowdSec-Prozess unter dieser Gruppe laufen. 1000 ist der übliche Default für den ersten Nutzer; läuft Traefik als root, ist die access.log meist world-readable und die GID ohnehin egal. Ist sie enger gesetzt, prüfe mit ls -l /var/log/traefik/access.log und trag die GID der Gruppe der Datei ein.
  • Das Log ist nur lesend (:ro) eingebunden. CrowdSec hängt im proxy-Netz, damit der Traefik-Bouncer die LAPI später unter crowdsec:8080 erreicht.

Starte den Container und prüfe, dass alles greift:

Terminal
docker compose up -d
docker exec crowdsec cscli collections list

Die Traefik-Collection und die HTTP-Szenarien sollten als enabled erscheinen:

Ausgabe
 crowdsecurity/base-http-scenarios    ✔️  enabled
 crowdsecurity/http-cve               ✔️  enabled
 crowdsecurity/traefik                ✔️  enabled

Prüfe, dass die Community-Blocklist aktiv ist – das kostet nichts und ist der halbe Nutzen von CrowdSec:

Terminal
docker exec crowdsec cscli capi status
Ausgabe
You can successfully interact with Central API (CAPI)
Sharing signals is enabled
Pulling community blocklist is enabled
Pulling blocklists from the console is enabled

Pulling community blocklist is enabled bestätigt, dass du die kollektive Sperrliste der CrowdSec-Community beziehst – Angreifer, die anderswo auffällig wurden, sind bei dir schon gesperrt, bevor sie es überhaupt versuchen.

Schritt 3: Den Traefik-Bouncer einbauen

Jetzt der Teil, der die Sperren durchsetzt. Der Bouncer ist ein Traefik-Plugin und besteht aus zwei Ergänzungen an deinem Traefik: das Plugin laden und eine Middleware definieren, die auf alle Apps wirkt.

Lade das Plugin, indem du im command-Block von ~/traefik/compose.yaml ergänzt:

YAML
      - "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
      - "--experimental.plugins.bouncer.version=v1.6.0"
      - "--entrypoints.websecure.http.middlewares=crowdsec@file"

Die letzte Zeile hängt die Bouncer-Middleware global an den websecure-Entrypoint – damit läuft jede HTTPS-Anfrage an jede App durch den Bouncer, ohne dass du jede App einzeln anfassen musst. Die Middleware selbst definierst du über den File-Provider.

Vorhandene Entrypoint-Middlewares nicht überschreiben

--entrypoints.websecure.http.middlewares ist einwertig: Hast du im Traefik-Aufbau an diesem Entrypoint schon Middlewares gesetzt (etwa Security-Header), überschreibt ein zweites Flag den vorhandenen Wert – dann fallen deine bisherigen Middlewares still weg. Setz also kein zweites Flag, sondern erweitere den bestehenden Wert komma-separiert, z. B. --entrypoints.websecure.http.middlewares=header@file,crowdsec@file. Nur wenn an diesem Entrypoint bisher keine Middleware hängt, fügst du das Flag wie oben neu hinzu.

Kein zweites File-Provider-Flag

Der Traefik-Aufbau hat den File-Provider in aller Regel schon aktiv (etwa als --providers.file.filename=… oder --providers.file.directory=…). Traefik erlaubt nicht gleichzeitig filename und directory – ergänze also kein zweites Flag blind. Lege die folgende Middleware-Datei entweder in dein bestehendes dynamic-Verzeichnis, oder – falls dein Provider auf eine einzelne filename-Datei zeigt – schreib den http.middlewares-Block einfach dort hinein. Die Pfade unten (~/traefik/dynamic bzw. /etc/traefik/dynamic) gelten nur, falls du schon ein Verzeichnis nutzt.

Lege die Middleware an (Pfad an deinen bestehenden File-Provider angepasst):

YAML
# ~/traefik/dynamic/crowdsec.yaml
http:
  middlewares:
    crowdsec:
      plugin:
        bouncer:
          enabled: true
          crowdsecMode: stream
          crowdsecLapiScheme: http
          crowdsecLapiHost: crowdsec:8080
          crowdsecLapiKey: "DEIN_BOUNCER_KEY"

crowdsecLapiKey ist derselbe Schlüssel wie in Schritt 2 – der Bouncer weist sich damit bei der LAPI aus. Mit crowdsecMode: stream zieht der Bouncer die Sperrliste in einem festen Intervall komplett vor und beantwortet jede Anfrage aus dem lokalen Cache – der vom Plugin empfohlene Modus, weil er die LAPI nicht bei jedem Request belastet.

Sorg dafür, dass Traefik das Verzeichnis auch sieht. Nutzt dein bestehender File-Provider schon ein gemountetes dynamic-Verzeichnis, liegt die neue Datei ohnehin darin – dann ist hier nichts zu tun. Legst du das Verzeichnis neu an, ergänze es im volumes-Block von Traefik:

YAML
      - ./dynamic:/etc/traefik/dynamic:ro

Starte Traefik neu, damit es das Plugin herunterlädt und die Middleware lädt:

Terminal
cd ~/traefik && docker compose up -d
docker compose logs -f traefik

Im Log darf kein ERR zum Plugin oder zur Middleware stehen (Traefik v3 loggt bei Standard-Level nur Fehler – eine ruhige Ausgabe ist das gute Zeichen). Prüfe von der CrowdSec-Seite, dass der Bouncer sich verbunden hat:

Terminal
docker exec crowdsec cscli bouncers list
Ausgabe
 Name     IP Address  Valid  Last API pull         Type
 TRAEFIK  172.19.0.2  ✔️      2026-07-23T10:49:58Z  Crowdsec-Bouncer-Traefik-Plugin

Ein gesetztes Last API pull beweist: Das Plugin spricht mit der LAPI.

Schritt 4: Scharf schalten und die Sperre testen

Ruf eine deiner Apps auf – sie sollte ganz normal antworten:

Terminal
curl -s -o /dev/null -w "%{http_code}\n" https://DEINE_APP.DEINE_DOMAIN/
Ausgabe
200

Jetzt sperrst du testweise eine IP von Hand und schaust, ob der Bouncer sie aussperrt. Nimm deine eigene öffentliche IP (curl -s https://ifconfig.me):

Terminal
docker exec crowdsec cscli decisions add --ip DEINE_TEST_IP --duration 5m --reason "test"

Neue Sperren greifen nicht sofort

Der Bouncer holt sich die Entscheidungen im Hintergrund ab und cached sie – bis eine frische Sperre wirkt, können bis zu ~60 Sekunden vergehen. Das ist kein Fehler, sondern gewollt (weniger Last auf der LAPI). Im stream-Modus kommt die Verzögerung vom Pull-Intervall (updateIntervalSeconds, Default 60 Sekunden), im live-Modus vom Per-IP-Cache jeder Entscheidung (defaultDecisionSeconds, Default 60 Sekunden). Im echten Betrieb ist das irrelevant; beim Testen musst du kurz warten. Wer die Reaktionszeit drücken will, senkt den jeweils passenden Wert – also updateIntervalSeconds (stream) bzw. defaultDecisionSeconds (live) – in der Middleware, statt den Modus zu wechseln.

Nach der Wartezeit bekommt die gesperrte IP nur noch:

Terminal
curl -s -o /dev/null -w "%{http_code}\n" https://DEINE_APP.DEINE_DOMAIN/
Ausgabe
403

Heb die Test-Sperre wieder auf:

Terminal
docker exec crowdsec cscli decisions delete --ip DEINE_TEST_IP

Schritt 5: Echte Angriffe erkennen lassen

Der eigentliche Sinn: CrowdSec sperrt von selbst, ohne dass du Regeln schreibst. Sobald ein Scanner deine Domain nach Pfaden abklopft (viele 404 in kurzer Zeit), greift das Szenario crowdsecurity/http-probing. So sieht eine echte, automatisch erkannte Sperre aus:

Terminal
docker exec crowdsec cscli alerts list
Ausgabe
 ID  value              reason                       country  as                      decisions
 4   Ip:109.250.21.197  crowdsecurity/http-probing   DE       8881 1&1 Versatel GmbH  ban:1
Terminal
docker exec crowdsec cscli decisions list
Ausgabe
 Source    Scope:Value        Reason                       Action  Country  AS
 crowdsec  Ip:109.250.21.197  crowdsecurity/http-probing   ban     DE       8881 1&1 Versatel GmbH

CrowdSec reichert die Sperre automatisch um Land und Provider (AS) an. Neben den selbst erkannten Angriffen tauchen hier mit der Zeit auch Sperren mit der Quelle lists:crowdsecurity/... auf – das ist die Community-Blocklist: IPs, die weltweit schon negativ aufgefallen sind, blockst du, bevor sie dich das erste Mal treffen.

Wer die Sperren lieber grafisch statt in cscli-Tabellen sieht, meldet die Instanz kostenlos in der CrowdSec-Console an (der Enroll-Befehl steht in „Wartung & Backups"). Dort erscheinen dieselben Entscheidungen aufbereitet:

Die CrowdSec-Console listet die aktiven Sperren als Tabelle: mehrere IP-Adressen mit der Aktion „ban", einer Ablaufzeit und dem auslösenden Szenario http-probing, jeweils „via crowdsec"
Die CrowdSec-Console (optional, kostenlos): dieselben Entscheidungen wie in cscli, nur grafisch

Die mit der Traefik-Collection installierten HTTP-Szenarien decken das ab, was echte Scanner den ganzen Tag versuchen: das Abklopfen nicht existierender Pfade (http-probing), das Suchen nach sensiblen Dateien und Backdoors, Path-Traversal- und SQL-Injection-/ XSS-Sondierungen, verdächtige User-Agents bekannter Scanner sowie die Ausnutzung bekannter CVEs (aus der Collection crowdsecurity/http-cve). Welche Szenarien aktiv sind, zeigt docker exec crowdsec cscli scenarios list.

Noch eine Stufe schärfer: AppSec (WAF)

Über das reine IP-Blocking hinaus kann CrowdSec als Web Application Firewall arbeiten und bösartige Requests direkt am Inhalt erkennen (Virtual Patching). Das Traefik-Plugin unterstützt diesen AppSec-Modus (ab Plugin 1.2.0 und CrowdSec 1.6.0). Für den Einstieg reicht das hier gezeigte Szenario-basierte Blocking; AppSec ist der lohnende nächste Ausbauschritt, wenn eine besonders exponierte App zusätzlichen Schutz braucht.

Ob CrowdSec deine Logs überhaupt liest, zeigt die Metrik-Übersicht:

Terminal
docker exec crowdsec cscli metrics
Ausgabe
| Source                           | Lines read | Lines parsed |
| file:/var/log/traefik/access.log | 54         | 54           |

Steht dort Lines read: 0, kommt kein Log an – dann zurück zu Schritt 1.

Schritt 6: Dich selbst nicht aussperren

Ein Fehlalarm gegen deine eigene Wartungs-IP ist ärgerlich. Trag deine feste IP als vertrauenswürdig in die Middleware ein – der Bouncer prüft sie dann gar nicht erst. Ergänze in ~/traefik/dynamic/crowdsec.yaml unter bouncer::

YAML
          clientTrustedIPs:
            - "DEINE_FESTE_IP/32"

Hast du dich doch einmal selbst gesperrt, hebt ein Befehl die Sperre sofort auf – und im Notfall kommst du über die VNC-Konsole im SCP immer noch auf den Server:

Terminal
docker exec crowdsec cscli decisions delete --ip DEINE_IP   # eine IP
docker exec crowdsec cscli decisions delete --all           # alle Sperren

Wenn es nicht funktioniert

Symptom: Eine frisch gesetzte Sperre wirkt nicht, die IP bekommt weiter 200.

Ursache & Lösung: Der Bouncer cached die Entscheidungen und zieht neue im stream-Modus erst nach dem Pull-Intervall (bis zu ~60 Sekunden). Kurz warten. Wer die Reaktionszeit drücken will, senkt updateIntervalSeconds in der Middleware (Schritt 3).

Symptom: cscli metrics zeigt für die Log-Datei Lines read: 0.

Ursache & Lösung: CrowdSec liest die Logs nicht. Prüfe, dass Traefik wirklich nach /var/log/traefik/access.log schreibt (Schritt 1), dass beide Container denselben Host-Pfad einbinden und dass die acquis.yaml genau diesen Pfad nennt.

Symptom: Nach dem Neustart von Traefik bekommen alle Apps einen Fehler oder laden nicht.

Ursache & Lösung: Im stream-Modus (oben gesetzt) kann der Bouncer nach mehreren fehlgeschlagenen LAPI-Pulls dichtmachen – gesteuert über updateMaxFailure. Ist die CrowdSec-LAPI nicht erreichbar, blockt der Bouncer dann im Zweifel, statt ungeschützt durchzulassen. Das ist sicher, macht CrowdSec aber zu einer kritischen Komponente: Läuft der crowdsec-Container nicht, ist deine Seite betroffen. Prüfe mit docker ps, dass er Up ist; restart: unless-stopped (oben gesetzt) holt ihn nach einem Neustart automatisch zurück.

Symptom: CrowdSec sperrt lauter harmlose oder gar keine echten Besucher – die erkannte IP ist immer die deines CDNs.

Ursache & Lösung: Sitzt ein CDN wie Cloudflare vor Traefik, sieht Traefik nur dessen IP. Dann die echte Client-IP aus dem X-Forwarded-For-Header lesen lassen: In der Middleware forwardedHeadersTrustedIPs mit den CDN-Netzen füllen – sonst sperrst du am Ende das CDN. Direkt an netcup (ohne CDN) ist hier nichts zu tun.

Symptom: docker compose up -d bei Traefik meldet einen Plugin-Fehler.

Ursache & Lösung: Traefik lädt das Plugin beim Start aus dem Netz. Prüfe, dass der Server raus darf (die netcup-Firewall sperrt ausgehend nichts, UFW ebenso wenig) und dass modulename und version exakt stimmen.

Wartung & Backups

  • Szenarien aktuell halten. CrowdSecs Stärke sind gepflegte Erkennungsregeln – zieh sie regelmäßig nach: docker exec crowdsec cscli hub update && docker exec crowdsec cscli hub upgrade. Das Image (crowdsecurity/crowdsec) und die Plugin-Version (v1.6.0) hebst du bewusst an; lies bei Hauptversionen die Release-Notes. CrowdSec entwickelt sich zügig – ein guter Kandidat für regelmäßige Versionsprüfung.

  • Zugriffs-Log rotieren. Traefiks /var/log/traefik/access.log wächst sonst unbegrenzt. Richte eine logrotate-Regel ein – zwingend mit copytruncate: Diese Variante kopiert die Datei weg und leert das Original an Ort und Stelle, statt es umzubenennen und neu anzulegen. Nur so behält CrowdSecs Tail den Dateizugriff (bei rename/create verlöre es den Handle und läse nichts mehr). Lege /etc/logrotate.d/traefik an:

    Ausgabe
    /var/log/traefik/access.log {
        daily
        rotate 7
        compress
        missingok
        notifempty
        copytruncate
    }

    CrowdSec liest nach dem Truncate an der wieder wachsenden Datei ganz normal weiter – ein Neustart des Containers ist nicht nötig.

  • fail2ban bleibt. Nimm fail2ban nicht heraus – es schützt weiter deinen SSH-Zugang auf Host-Ebene, während CrowdSec die Web-Apps abdeckt. Zwei Werkzeuge, zwei Zuständigkeiten.

  • Backups. Sichern musst du vor allem deine Konfiguration: den Ordner ~/crowdsec (compose.yaml, acquis.yaml) und ~/traefik/dynamic/crowdsec.yaml mit dem Bouncer-Key. Nimm sie in dein Restic-Backup auf. Die Sperrlisten selbst sind flüchtig und bauen sich von allein wieder auf – kein Backup nötig.

  • Optional: das CrowdSec-Dashboard. Wer die Angriffe grafisch sehen will, meldet die Instanz kostenlos in der CrowdSec Console an (docker exec crowdsec cscli console enroll DEIN_ENROLL_KEY). Die Community-Blocklist läuft auch ohne Anmeldung.

  • In dein Monitoring einbinden. cscli metrics liefert dir Zahlen fürs Terminal; für Dauerbeobachtung stellt CrowdSec einen Prometheus-Endpunkt bereit (in der Config unter prometheus: aktivieren), den du an deinen Grafana-Stack hängen kannst. Dann siehst du Sperren nach Herkunft und Szenario auf einen Blick:

    Ein Grafana-Dashboard mit CrowdSec-Metriken: rund 15.000 aktive Sperren gesamt, aufgeschlüsselt nach Herkunft (Community-Blocklist vs. lokal erkannt) und nach Szenario wie ssh:bruteforce, generic:scan und http-probing
    CrowdSec in Grafana: aktive Sperren nach Herkunft und Szenario, gespeist aus dem Prometheus-Endpunkt

  • Ehrlich zum Aufwand: Nach dem Aufsetzen läuft CrowdSec weitgehend allein. Plane einmal im Monat den hub upgrade und einen Blick auf cscli metrics und die Container-Version ein.

Feedback per E-Mail: feedback@serverkueche.de

Das könnte dir auch schmecken