> ## Content Index
> Fetch the complete content index at: https://blog.cube-m.de/llms.txt
> Use this file to discover other available public pages before exploring further.

# 10. CrowdSec – Deinen VPS gegen Angriffe schützen
- URL: https://blog.cube-m.de/10-crowdsec-deinen-vps-gegen-angriffe-schutzen/
- Published: 2026-06-24T13:52:38.000Z
- Updated: 2026-06-29T12:06:41.000Z
- Description: CrowdSec liest die Logs von Caddy und Authelia mit, erkennt Brute-Force und Scanner, und blockt Angreifer-IPs direkt im Reverse Proxy. Inklusive Community-Blocklist und optionaler Web-Oberfläche.
- Author: Martin
- Tags: VPS, Infrastruktur, CrowdSec, Server-Sicherheit, Caddy, docker, #Import 2026-09-06 19:52

Seit Artikel 07 ist Caddy der eine kontrollierte Eingang von außen. UFW erlaubt 80 und 443, alles dahinter läuft intern. Seit Artikel 09 sitzt vor jeder persönlichen App zusätzlich Authelia mit 2FA. Das ist solide – aber es ist statisch. Wer dich angreift, kommt zwar nicht durch, aber er kann es beliebig oft versuchen. Sein nächster Versuch sieht für dein Setup genauso aus wie der erste.

CrowdSec ergänzt genau diese Ebene: Es liest die Logs deiner öffentlichen Dienste, erkennt aggressive Muster (Brute Force, Scans, CVE-Versuche, bekannte Bots) und sagt Caddy, dass die jeweilige IP für eine Weile draußen bleibt. Und – das ist der eigentliche Trick – es nutzt zusätzlich eine community-gepflegte Liste bereits anderswo aufgefallener IPs. Wenn dich also eine IP angreift, die letzte Woche schon hundert andere CrowdSec-Nutzer angegriffen hat, bekommt sie von dir gar keinen Login-Schirm mehr zu sehen – sondern direkt eine Fehlerseite, ohne dass Authelia oder die App überhaupt befragt wird.

Das Plugin in Caddy ist seit Artikel 07 schon eingebaut, nur inaktiv. In diesem Artikel aktivierst du es.

## Am Ende hast du

- CrowdSec liest die Logs deiner Web-Dienste mit und erkennt typische Angriffsmuster: massenhaft Passwort-Versuche, automatisches Abklappern bekannter Sicherheitslücken, Bot-Scans
- Erkannte Angreifer werden für eine Weile gesperrt – sie sehen statt deiner App nur eine Fehlerseite
- Die globale CrowdSec-Community-Blocklist ist aktiv: IPs, die gerade *anderswo* attackieren, werden bei dir präventiv geblockt, noch bevor sie es bei dir versuchen
- Deine eigene Heim-IP ist auf einer Whitelist und kommt nicht aus Versehen in eigene Sperren
- Du kannst per Kommandozeile jederzeit sehen, wer gerade gesperrt ist und warum
- Optional: Eine schlanke Web-Oberfläche zeigt Alerts und Bans direkt im Browser
- Optional: Eine zusätzliche Filterschicht prüft den Inhalt jedes einzelnen Requests und blockt typische Angriffe gegen Webanwendungen – noch bevor sie deine App erreichen

## Voraussetzungen

- Artikel 02–07 abgeschlossen; Artikel 09, wenn du Authelia-Logs mit auswerten willst
- Caddy läuft mit dem Image `ghcr.io/serfriz/caddy-crowdsec:2` (Artikel 07) und schreibt Access-Logs nach `/srv/docker/stacks/caddy/logs/`
- Komodo läuft, Server `homelab` ist eingebunden
- Eine zweite externe IP zum Testen (z. B. dein Smartphone im Mobilfunknetz) – damit du dich nicht aus Versehen selbst aussperrst

## 1\. Wie CrowdSec funktioniert

Bevor wir konfigurieren, ein Bild der Funktionsweise und die wichtigsten Begriffe – die tauchen im weiteren Artikel immer wieder auf:

- **Bouncer** ist der Türsteher – der Teil, der eine IP tatsächlich blockiert. In unserem Setup ist das Bouncer-Plugin schon im Caddy-Container drin.
- **Decision** ist eine konkrete Entscheidung: „IP 1.2.3.4 für 4 Stunden sperren". CrowdSec sammelt diese Decisions in einer kleinen Datenbank, der Bouncer holt sie sich von dort ab.
- **Parser** liest eine Logzeile und macht daraus strukturierte Daten (IP, URL, Status-Code).
- **Scenario** ist eine Regel: „wenn Bedingung X sich Y-mal in Z Sekunden wiederholt – dann eine Decision erzeugen". Parser und Scenarios kommen fertig aus dem CrowdSec-Hub, du installierst sie als **Collection** (Bundle für einen bestimmten Dienst, z. B. Caddy oder Authelia).

**Was CrowdSec bringt:**

- **Verhaltenserkennung.** Erkennt nicht nur einzelne böse Requests, sondern Muster über Zeit. Ein Login-Versuch ist harmlos, dreißig in zwei Minuten sind ein Brute-Force-Angriff.
- **Community-Blocklist.** Greift auf die zentrale CrowdSec-Datenbank zu. IPs, die anderswo auffällig waren, blockst du, bevor sie überhaupt einen Versuch unternehmen.
- **App-übergreifend.** Eine IP, die in Authelia 5× das Passwort falsch eintippt, sieht danach auch keine Vaultwarden-Seite, kein Immich, kein Ghost mehr – weil der Block beim Türsteher Caddy greift, nicht erst in der App.
- **Updates ohne Nachpflege.** Parser, Scenarios und CVE-Patterns kommen aus dem CrowdSec-Hub und werden automatisch aktualisiert.

**Was CrowdSec nicht ersetzt:**

- **Kein Ersatz für Authelia.** Authelia kontrolliert *wer* reinkommt (Account + 2FA). CrowdSec kontrolliert *welche IPs überhaupt fragen dürfen*. Beide Schichten zusammen sind sinnvoll, einzeln nur halb so wirksam.
- **Kein Ersatz für die Firewall.** UFW erlaubt 80/443 öffentlich, alles andere ist zu. CrowdSec filtert nur, was bei Caddy ankommt – andere Ports schließt weiterhin UFW.
- **Kein Ersatz für ein Backup.** Wenn ein Angreifer doch durchkommt, ändert CrowdSec daran nichts. Es macht es nur schwieriger, überhaupt anzukommen.

## 2\. Log-Pfade freigeben

CrowdSec braucht Lesezugriff auf die Logs von Caddy und Authelia. Die Verzeichnisse liegen auf dem Host; CrowdSec läuft im Container als User `nobody` (UID 65534).

Per SSH prüfen, dass Logs existieren:

```bash
ls /srv/docker/stacks/caddy/logs/
ls /srv/docker/stacks/authelia/log/

```

*Erwartung:* `.log`\-Dateien in beiden Verzeichnissen. Falls leer: kurz `https://auth.deine-domain.de` im Browser aufrufen – das reicht für einen ersten Eintrag.

Dann Leserechte setzen:

```bash
chmod o+rx /srv/docker/stacks/caddy/logs/
chmod o+rx /srv/docker/stacks/authelia/log/

```

> **Was `o+rx` bedeutet:** `o` steht für „others" – alles, was weder Owner noch Group ist. `r` \= lesen, `x` auf einem Verzeichnis = hineingehen dürfen. Dateien darin behalten ihre eigenen Rechte. Kein `chmod 777` nötig.

## 3\. Den Bouncer-Schlüssel vorab erzeugen

CrowdSec und der Caddy-Bouncer brauchen einen gemeinsamen Schlüssel, mit dem sich der Bouncer ausweist. Den erzeugen wir einmal vorab und tragen ihn in beide `.env`\-Dateien ein – CrowdSec registriert ihn beim ersten Start automatisch, Caddy benutzt ihn fürs Plugin.

Auf dem Server:

```bash
openssl rand -hex 32

```

Den 64-Zeichen-Hex-Wert in den Passwort-Manager kopieren. Du brauchst ihn gleich zweimal: in der `.env` des CrowdSec-Stacks (als `CROWDSEC_BOUNCER_KEY`) und in der `.env` des Caddy-Stacks (als `CROWDSEC_API_KEY`). Beide Werte müssen exakt gleich sein.

> **Warum nicht erst CrowdSec starten und dann `cscli bouncers add` benutzen?**
> 
> Funktioniert auch – und ist der Weg, den die meisten Tutorials zeigen. Dafür müsstest du aber CrowdSec hochfahren, dich ins Terminal des Containers verbinden, den dort erzeugten Schlüssel in die Caddy-`.env` kopieren und Caddy dann neu deployen. Mit `BOUNCER_KEY_<name>` als Umgebungsvariable nimmt CrowdSec den Schlüssel beim ersten Start aus der `.env` und registriert ihn von selbst. Du kannst beide Stacks in beliebiger Reihenfolge deployen, und der Schlüssel existiert nur an zwei Stellen, die du eh kennst.

## 4\. Stack in Komodo anlegen

In Komodo (`http://homelab:9120`):

1. Links **Stacks** → oben rechts **\+ New Stack**
2. **Name:** `crowdsec`
3. **Server:** `homelab`
4. **Stack Type:** `UI Defined`
5. **Create**

## 5\. compose.yaml

```yaml
name: crowdsec

services:
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    restart: unless-stopped
    env_file: .env
    environment:
      COLLECTIONS: "crowdsecurity/caddy crowdsecurity/http-cve LePresidente/authelia crowdsecurity/whitelist-good-actors"
      BOUNCER_KEY_caddy: ${CROWDSEC_BOUNCER_KEY}
    volumes:
      - ./config:/etc/crowdsec
      - ./data:/var/lib/crowdsec/data
      - /srv/docker/stacks/caddy/logs:/var/log/caddy:ro
      - /srv/docker/stacks/authelia/log:/var/log/authelia:ro
    networks:
      - homelab_net

networks:
  homelab_net:
    external: true

```

| Block                                | Bedeutung                                                                                                                                                                                                                     |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| image: crowdsecurity/crowdsec:latest | CrowdSec pflegt leider keinen stabilen Major-Tag wie caddy:2. :latest ist der empfohlene Stable-Tag. Mit **Poll for Updates an, Auto-Update aus** in Komodo siehst du neue Versionen und entscheidest selbst wann du deployst |
| COLLECTIONS                          | Vier Bundles: Caddy-Parser + HTTP-Scenarios, CVE-Patterns für bekannte Web-Exploits, Authelia-Parser + Brute-Force-Erkennung, Whitelist für bekannte gute Crawler (Googlebot etc.)                                            |
| BOUNCER\_KEY\_caddy                  | Wird beim ersten Start als Bouncer mit Namen caddy registriert                                                                                                                                                                |
| ./config \+ ./data                   | Konfiguration und Datenbank – persistiert über Updates. Die acquis.yaml aus Schritt 6 liegt in ./config und landet damit unter /etc/crowdsec. ./data ist Pflicht seit CrowdSec 1.7                                            |
| Caddy-Logs + Authelia-Logs (:ro)     | Absoluter Pfad, weil diese Stacks in anderen Verzeichnissen liegen. Read-only                                                                                                                                                 |
| Kein ports:                          | CrowdSec ist nur im internen Docker-Netz erreichbar; Caddy spricht es unter http://crowdsec:8080 an                                                                                                                           |

> **Warum keine SSH/Linux-Collection?**
> 
> Übliche CrowdSec-Anleitungen werfen `crowdsecurity/sshd` mit rein, um SSH-Brute-Force zu erkennen. In diesem Setup ist Port 22 seit Artikel 05 öffentlich nicht erreichbar – SSH läuft nur über Tailscale. Ein SSH-Parser auf einem Log, das öffentlich niemand mehr befüllen kann, ist totes Gewicht.

## 6\. .env

```
# Pre-shared Bouncer-Key (siehe Schritt 3)
# Identisch mit CROWDSEC_API_KEY in der .env des caddy-Stacks
CROWDSEC_BOUNCER_KEY=hier-den-openssl-key-aus-schritt-3-einsetzen

```

```bash
chmod 600 /srv/docker/stacks/crowdsec/.env

```

> Der Schlüssel ist das Login zwischen Caddy und CrowdSec. Wer ihn hat, kann beliebige IPs sperren oder freigeben. Verlust ist kein Drama – neuen erzeugen, in beiden `.env`\-Dateien austauschen, beide Stacks redeployen. Trotzdem genauso behandeln wie das Authelia-JWT-Secret aus Artikel 09.

## 7\. acquis.yaml anlegen

`COLLECTIONS` sagt CrowdSec *wie* es Logs parsen soll. Die `acquis.yaml` sagt *wo* die Logs liegen. Ohne diese Datei startet CrowdSec zwar – aber es liest keine einzige Log-Zeile und schlägt nie an. Das ist der häufigste Fehler bei CrowdSec-Docker-Setups.

Die Datei muss **vor dem ersten Deploy** manuell angelegt werden. Sie gehört ins `config`\-Verzeichnis, das CrowdSec als `/etc/crowdsec` einhängt:

```bash
mkdir -p /srv/docker/stacks/crowdsec/config
nano /srv/docker/stacks/crowdsec/config/acquis.yaml

```

Inhalt:

```yaml
---
filenames:
  - /var/log/caddy/*.log
labels:
  type: caddy
---
filenames:
  - /var/log/authelia/*.log
labels:
  type: authelia
---

```

Mit `Strg + X`, `Y`, `Enter` speichern.

Zwei Blöcke, getrennt durch `---`. Der erste beobachtet alle `.log`\-Dateien unter `/var/log/caddy/` (auch rotierte Dateien wie `access.log.001`) und schickt sie durch den Caddy-Parser. Der zweite macht dasselbe für Authelia.

> **Wenn du Artikel 09 übersprungen hast**
> 
> Dann lass den zweiten Block einfach weg. Falls du Authelia später nachrüstest, fügst du ihn dann hinzu und machst einen Stack-Redeploy.

> **Caddy-Log-Format muss JSON sein**
> 
> Der CrowdSec-Caddy-Parser erwartet JSON. Im `(public)`\-Snippet aus Artikel 07 ist das bereits eingestellt (`format json`). Falls du dort etwas verändert hast, wird der Parser nichts erkennen.

## 8\. Erstmals deployen

**Poll for Updates:** `on`  
**Auto Update:** `off`

**Deploy** klicken.

**Logs prüfen** (Komodo → Stack `crowdsec` → Logs):

```
level=info msg="CrowdSec ..."
level=info msg="Adding file /var/log/caddy/access.log to filelist"
level=info msg="Adding file /var/log/authelia/authelia.log to filelist"
level=info msg="loaded N parsers, N scenarios"
level=info msg="CrowdSec Local API listening on 0.0.0.0:8080"

```

**Collections prüfen:**

```bash
docker exec crowdsec cscli collections list

```

*Erwartung:* Vier Einträge (`crowdsecurity/caddy`, `crowdsecurity/http-cve`, `LePresidente/authelia`, `crowdsecurity/whitelist-good-actors`), Status `enabled`.

**Bouncer-Registrierung prüfen:**

```bash
docker exec crowdsec cscli bouncers list

```

*Erwartung:* Eine Zeile `caddy` mit `valid: true`. Die Spalte `Last Pull` ist noch leer – Caddy hat sich noch nicht verbunden. Das passiert im nächsten Schritt.

Wenn eine Collection nicht installiert wurde (Netz-Hänger, Hub offline): Stack einmal redeployen, oder im Container:

```bash
docker exec crowdsec cscli hub update && docker exec crowdsec cscli hub upgrade
```

## 9\. Caddy einbinden

Jetzt das Stück, das in Artikel 07 vorbereitet wurde: zwei kleine Edits im Caddyfile, eine Zeile in der `.env`, ein Stack-Redeploy.

### 9.1 Caddy-`.env` ergänzen

Im Stack `caddy` → `.env`\-Tab:

```
CROWDSEC_API_KEY=hier-den-gleichen-key-wie-im-crowdsec-stack-einsetzen

```

**Exakt derselbe Wert** wie `CROWDSEC_BOUNCER_KEY` im `crowdsec`\-Stack.

### 9.2 Globalen CrowdSec-Block ins Caddyfile

Im Caddyfile, im globalen Options-Block ganz oben:

```caddyfile
{
    email {$CADDY_EMAIL}

    order crowdsec first

    crowdsec {
        api_url         http://crowdsec:8080
        api_key         {$CROWDSEC_API_KEY}
        ticker_interval 15s
    }
}

```

| Direktive            | Bedeutung                                                                                                              |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| order crowdsec first | Erzwingt, dass die IP-Prüfung *vor* allen anderen Handlern läuft. **Pflicht** – gehört zwingend in den globalen Block. |
| api\_url             | Service-Name crowdsec, Port 8080 – beide Container im homelab\_net                                                     |
| api\_key             | Wird aus der .env eingesetzt. {$VAR} ist Caddys Syntax dafür                                                           |
| ticker\_interval 15s | Wie oft der Bouncer neue Decisions abholt. Default wäre 60s; mit 15s reagierst du schneller                            |

### 9.3 `crowdsec`\-Direktive ins `(public)`\-Snippet

Im `(public)`\-Snippet direkt unter dem Snippet-Header:

```caddyfile
(public) {
    crowdsec

    encode zstd gzip

    log {
        output file /var/log/caddy/access.log {
            roll_size 10MiB
            roll_keep 5
        }
        format json
    }

    header {
        Strict-Transport-Security "max-age=31536000;"
        X-Content-Type-Options    "nosniff"
        Referrer-Policy           "strict-origin-when-cross-origin"
        -Server
    }
}

```

Jede Site, die `import public` macht, durchläuft jetzt vor dem Reverse-Proxy den `crowdsec`\-Handler. Gebannte IPs bekommen `403 Forbidden`, alles andere geht durch wie vorher.

`roll_size 10MiB` / `roll_keep 5` – Log-Rotation: bei 10 MB wird rotiert, maximal 5 alte Dateien. Ohne Rotation wächst die Datei auf einem öffentlichen Server schnell an.

### 9.4 Caddy-Stack neu deployen

Weil sich die `.env` geändert hat, reicht kein Reload – wir brauchen einen vollen Redeploy. In Komodo: Stack `caddy` → **Deploy**.

> **Warum Redeploy und nicht nur Caddy-Reload?**
> 
> `.env`\-Variablen werden nur beim *Container-Start* eingesetzt, nicht bei einem Reload. Ohne Redeploy bleibt `{$CROWDSEC_API_KEY}` ein leerer String, und der Bouncer kann sich nicht authentifizieren. Der Redeploy dauert drei Sekunden – Caddy holt seine Zertifikate aus dem `/data`\-Volume und ist sofort wieder am Netz.

**Logs nach dem Redeploy:**

```
{"level":"info","logger":"crowdsec","msg":"initializing streaming bouncer"}
{"level":"info","logger":"crowdsec","msg":"first decisions fetched"}

```

`cscli bouncers list` zeigt jetzt für `caddy` auch eine aktuelle `Last Pull`\-Zeit. Wenn ja – die Verbindung steht.

## 10\. Funktionstest mit fremder IP

Wir lösen den Schutz einmal absichtlich aus, ohne einen echten Angriff simulieren zu müssen.

**Schritt 1:** Eine IP wählen, von der aus du testen kannst. Smartphone ins Mobilfunknetz schalten (WLAN aus), auf [ifconfig.me](https://ifconfig.me/?ref=blog.cube-m.de) die externe IP ablesen – nennen wir sie `203.0.113.42`.

**Schritt 2:** Auf dem Server einen Ban setzen:

```bash
docker exec crowdsec cscli decisions add --ip 203.0.113.42 --duration 5m --reason "test"

```

**Schritt 3:** Mit dem Smartphone (im Mobilfunknetz) deine Test-Subdomain aufrufen.

*Erwartung:* `403 Forbidden` – innerhalb von maximal 15 Sekunden nach dem `decisions add`.

**Schritt 4:** Ban wieder aufheben:

```bash
docker exec crowdsec cscli decisions delete --ip 203.0.113.42

```

Smartphone-Browser neu laden – die Seite kommt zurück. (Im Zweifel den Privat-Modus nutzen, damit kein Cache stört.)

Wenn das funktioniert hat: Der Bouncer ist aktiv, CrowdSec → Caddy → Browser ist eine geschlossene Kette.

> **Warum nicht einfach falsches Authelia-Passwort eintippen?**
> 
> Authelia hat sein eigenes Rate-Limiting (`regulation`). Du wirst zuerst von Authelia gebannt (Account-Sperre), bevor du genug Versuche für einen CrowdSec-Trigger zusammen hast. Der Test von einer fremden IP ist sauberer und definitiver.

## 11\. cscli – die wichtigsten Befehle für den Alltag

```bash
# Wer ist gerade gebannt?
docker exec crowdsec cscli decisions list

# Was hat zur Ban-Entscheidung geführt?
docker exec crowdsec cscli alerts list

# Eine IP manuell bannen oder freigeben
docker exec crowdsec cscli decisions add --ip 1.2.3.4 --duration 4h --reason "manuell"
docker exec crowdsec cscli decisions delete --ip 1.2.3.4

# Bouncer-Status: wer hat sich wann Decisions geholt?
docker exec crowdsec cscli bouncers list

# Wie viele Log-Zeilen werden geparst, wie viele Bouncer-Anfragen?
docker exec crowdsec cscli metrics

# Hub-Updates ziehen (Parser, Scenarios, CVE-Patterns)
docker exec crowdsec cscli hub update && docker exec crowdsec cscli hub upgrade

```

> **Wenn `decisions list` plötzlich tausende Einträge zeigt**
> 
> Das ist Absicht – das sind die Community-Decisions aus Schritt 12\. Deine eigenen Bans erkennst du am Reason, z. B. `LePresidente/authelia-bf` für Authelia-Brute-Force.

## 12\. Die Community-Blocklist aktivieren

Bis hierhin schützt CrowdSec dich auf Basis deiner eigenen Logs. Das wirklich starke Feature ist die Community-Blocklist: IPs, die anderswo schon auffällig waren, werden bei dir präventiv geblockt.

Dafür einmalig bei der CrowdSec-Console registrieren:

**Schritt 1:** Unter [app.crowdsec.net](https://app.crowdsec.net/?ref=blog.cube-m.de) Account anlegen (kostenlos, E-Mail genügt).

**Schritt 2:** **Security Engines** → **Add Security Engine** → Enrollment-Key kopieren.

**Schritt 3:** Auf dem Server:

```bash
docker exec crowdsec cscli console enroll --name homelab DEIN-ENROLLMENT-KEY

```

**Schritt 4:** Im Browser auf der Console: **Accept Enrollment** klicken.

**Schritt 5:** Stack in Komodo neu **Deploy**en (kurzer Restart, damit die Verbindung aktiv wird).

**Prüfen:**

```bash
docker exec crowdsec cscli capi status

```

*Erwartung:* `You can successfully interact with Central API (CAPI)`.

Ab jetzt holt CrowdSec mehrmals täglich Community-Decisions. Ein paar Minuten später solltest du in `cscli decisions list` tausende Einträge sehen – das sind die globalen Banlists.

> **Datenschutz – was wird geteilt?**
> 
> CrowdSec sendet anonymisierte Signaturen deiner Alerts an die zentrale API – Meldungen der Form „IP X hat Pfad Y attackiert". Keine Logs, keine Inhalte, keine Nutzer-Daten. Wer das nicht will, kann diesen Schritt überspringen – CrowdSec bleibt dann ein rein lokaler Schutz.

## 13\. Die eigene IP whitelisten

Wenn du dich von zu Hause regelmäßig bei Authelia einloggst und mal das Passwort vertippst, kannst du dich selbst bannen. Die `whitelist-good-actors`\-Collection schützt bekannte Crawler wie Googlebot, aber nicht dich.

CrowdSec liest Whitelist-Regeln aus dem Verzeichnis `config/parsers/s02-enrich/`. Per SSH (das Verzeichnis ggf. zuerst anlegen):

```bash
mkdir -p /srv/docker/stacks/crowdsec/config/parsers/s02-enrich
nano /srv/docker/stacks/crowdsec/config/parsers/s02-enrich/whitelists.yaml

```

Inhalt:

```yaml
name: martin/whitelist-homeip
description: "Whitelist eigene Heim-IP"
whitelist:
  reason: "Eigene IP – nie bannen"
  ip:
    - "203.0.113.7"
  cidr:
    - "198.51.100.0/24"

```

`203.0.113.7` durch deine wirkliche externe IP ersetzen (auf [ifconfig.me](https://ifconfig.me/?ref=blog.cube-m.de) nachsehen). Stack einmal redeployen, damit die Datei greift.

**Test:**

```bash
docker exec crowdsec cscli decisions add --ip <deine-IP> --reason "selftest"
docker exec crowdsec cscli decisions list

```

Die Decision sollte gar nicht erscheinen – die Whitelist hat sie vorher aussortiert.

> **Bei dynamischer Heim-IP**
> 
> Bei den meisten deutschen Providern wechselt die IP alle 24 Stunden. Pragmatische Alternative: Tailscale läuft eh – wenn du von zu Hause auf Authelia gehst, geh über den `100.x.x.x`\-Tailscale-Hostnamen statt über die öffentliche Subdomain. Dann ist deine ISP-IP gar nicht erst beteiligt.

## 14\. Web-Oberfläche: crowdsec-web-ui (optional)

`cscli decisions list` reicht für den täglichen Betrieb. Wer lieber eine grafische Übersicht will – Alerts, aktive Bans, Angriffstypen, Geo-Herkunft – bekommt das mit [crowdsec-web-ui](https://github.com/TheDuffman85/crowdsec-web-ui?ref=blog.cube-m.de): eine schlanke React-App, die direkt gegen die CrowdSec-API spricht. Ein Container, keine externe Datenbank, aktiv gepflegt.

### 14.1 Machine Account anlegen

Die Web-UI authentifiziert sich über ein Machine-Konto – das ist ein anderes Konzept als der Bouncer-Key. Ein Machine-Konto hat Lese- und Schreibzugriff auf die LAPI (es kann z. B. Bans aus der UI heraus entfernen).

```bash
# Passwort generieren
openssl rand -hex 32

# Machine-Konto anlegen
docker exec crowdsec cscli machines add crowdsec-web-ui --password <generiertes-passwort> -f /dev/null

```

Das Flag `-f /dev/null` verhindert, dass `cscli` die Credentials-Datei des CrowdSec-Containers überschreibt.

### 14.2 compose.yaml erweitern

Den `crowdsec-ui`\-Service zum bestehenden Stack hinzufügen:

```yaml
name: crowdsec

services:
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    restart: unless-stopped
    env_file: .env
    environment:
      COLLECTIONS: "crowdsecurity/caddy crowdsecurity/http-cve LePresidente/authelia crowdsecurity/whitelist-good-actors"
      BOUNCER_KEY_caddy: ${CROWDSEC_BOUNCER_KEY}
    volumes:
      - ./config:/etc/crowdsec
      - ./data:/var/lib/crowdsec/data
      - /srv/docker/stacks/caddy/logs:/var/log/caddy:ro
      - /srv/docker/stacks/authelia/log:/var/log/authelia:ro
    networks:
      - homelab_net

  crowdsec-ui:
    image: ghcr.io/theduffman85/crowdsec-web-ui:latest
    container_name: crowdsec-ui
    restart: unless-stopped
    environment:
      - CROWDSEC_URL=http://crowdsec:8080
      - CROWDSEC_USER=crowdsec-web-ui
      - CROWDSEC_PASSWORD=${CROWDSEC_UI_PASSWORD}
      - CROWDSEC_LOOKBACK_PERIOD=7d
      - CROWDSEC_REFRESH_INTERVAL=30s
    volumes:
      - ./ui-data:/app/data
    networks:
      - homelab_net

networks:
  homelab_net:
    external: true

```

In der `.env` ergänzen:

```
CROWDSEC_BOUNCER_KEY=...
CROWDSEC_UI_PASSWORD=<das-generierte-passwort-von-oben>

```

### 14.3 Caddy-Route anlegen

Per SSH ins Caddyfile:

```caddyfile
crowdsec.deine-domain.de {
    import public
    import auth
    reverse_proxy crowdsec-ui:3000
}

```

Die UI läuft hinter Authelia – kein Login in der App selbst nötig.

Caddy neu laden:

```bash
docker exec caddy caddy validate --config /etc/caddy/Caddyfile
docker exec caddy caddy reload --config /etc/caddy/Caddyfile

```

Stack `crowdsec` in Komodo neu **Deploy**en.

> **crowdsec-web-ui hat keine eigene Authentifizierung** – bewusstes Design der Entwickler. `import auth` im Caddy-Block ist daher Pflicht, nicht optional.

## 15\. Optional: AppSec – Schutz vor SQLi, XSS und bekannten CVE-Exploits

> Beim ersten Lesen ruhig überspringen. Mit den bisherigen Schritten hast du einen vollwertigen IP-Reputations-Schutz. AppSec ist eine zusätzliche Schicht, die man später nachrüsten kann.

Bis hierhin sperrt CrowdSec **IPs**. Wer aber bisher unauffällig war, kommt mit jedem Request durch – Caddy schaut nicht in den Request rein. AppSec dreht das um: Es prüft jeden Request *vor* der Weiterleitung an die App. SQLi, XSS, Path Traversal, bekannte CVE-Exploits werden direkt blockiert.

### 15.1 AppSec-Collections nachinstallieren

In der `compose.yaml` des `crowdsec`\-Stacks die `COLLECTIONS`\-Zeile erweitern:

```yaml
COLLECTIONS: "crowdsecurity/caddy crowdsecurity/http-cve LePresidente/authelia crowdsecurity/whitelist-good-actors crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules"

```

### 15.2 AppSec-Datasource in der `acquis.yaml`

Einen dritten Block anhängen:

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

```

### 15.3 Caddyfile erweitern

Im globalen Block:

```caddyfile
crowdsec {
    api_url         http://crowdsec:8080
    api_key         {$CROWDSEC_API_KEY}
    ticker_interval 15s
    appsec_url      http://crowdsec:7422
}

```

Im `(public)`\-Snippet direkt unter `crowdsec`:

```caddyfile
(public) {
    crowdsec
    appsec
    encode zstd gzip
    ...
}

```

**Reihenfolge wichtig:** Erst `crowdsec` (IP-Check – schnell), dann `appsec` (Inhaltsprüfung – aufwändiger). Bekannte böse IPs filtern wir raus, *bevor* sie durch die WAF müssen.

### 15.4 Beide Stacks neu deployen

Stack `crowdsec` redeployen (für den neuen acquis-Block), Stack `caddy` redeployen (für die neue `appsec`\-Direktive).

> **AppSec und False Positives**
> 
> Die `appsec-virtual-patching`\-Collection ist eng gefasst: konkrete CVE-Exploits, kaum Bauchgefühl. False-Positive-Risiko nahe null. Die `appsec-generic-rules` ist aggressiver – sie kann Admin-Backends, die Sonderzeichen in URLs erwarten, anders bewerten. Empfehlung: mit `virtual-patching` starten, `generic-rules` nur dazuholen, wenn du Zeit zum Tunen hast. `cscli alerts list` zeigt dir bei Problemen, welche Regel getriggert hat.

## 16\. Wartung

CrowdSec ist erfreulich pflegeleicht – zwei Dinge regelmäßig tun:

**Hub-Updates** (Parser, Scenarios, CVE-Patterns) – etwa monatlich:

```bash
docker exec crowdsec cscli hub update
docker exec crowdsec cscli hub upgrade

```

Läuft beim Container-Start zwar automatisch, aber wenn der Container monatelang durchläuft, passiert nichts. Ein manueller Lauf alle paar Wochen kostet nichts.

**Image-Updates** – Komodo zeigt sie durch **Poll for Updates** an. Bei neuer Version: Stack redeployen. Der `./data`\-Mount behält die Datenbank mit Bouncer-Registrierung und Decisions – nach dem Upgrade läuft alles weiter.

**Eigene Whitelist-IP** – wann immer dein Provider die IP wechselt und du es merkst (meist erst, wenn du dich aus Versehen selbst bannst). Datei aus Schritt 13 editieren, Stack redeployen.

## Und jetzt?

Das war's mit Phase 2 – Infrastruktur. Du hast jetzt:

- **Tailscale** für sicheren Admin-Zugang ohne offene SSH-Ports (Artikel 05)
- **Komodo** für Docker-Verwaltung im Browser (Artikel 06)
- **Caddy** als einzigen kontrollierten öffentlichen Eingang mit HTTPS (Artikel 07)
- **Mail-Relay** für App-Benachrichtigungen (Artikel 08)
- **Authelia** als 2FA-Login-Schicht vor persönlichen Apps (Artikel 09)
- **CrowdSec** als IP-Reputation- und Angriffserkennungs-Layer (dieser Artikel)

Jede neue App in Phase 3 hängt sich in dieses Geflecht ein, ohne dass du an der Infrastruktur etwas ändern musst. Sie importiert in Caddy je nach Zugriffsbedarf `public` oder `public` \+ `auth`, sie nutzt den Mail-Relay für Benachrichtigungen, und sie wird automatisch von CrowdSec mitgeschützt – weil der Bouncer in Caddy davor sitzt.

Im nächsten Artikel kommt der **App-Baukasten** – der konzeptionelle Übergang von Infrastruktur zu Apps.

👉 **Nächster Artikel:** [11\. App-Baukasten verstehen und nutzen](#)