Wer CrowdSec produktiv im Einsatz hat, kennt das: Jeder Blick auf aktuelle Bans, jede Whitelist-Anpassung, jede Alert-Analyse läuft über docker exec crowdsec cscli .... Das funktioniert, ist aber auf Dauer mühsam, vor allem wenn man mal eben schauen will, warum eine bestimmte IP gerade gebannt ist, oder eine neue AppSec-Whitelist für einen Self-Hosted-Dienst anlegen will. Die Antwort von CrowdSec selbst auf dieses Problem heißt CrowdSec Console, welche als Web-Oberfläche mit Dashboard, Alert-Übersicht, Bouncer-Management und mehr zur Verfügung gestellt wird. Wer aber eine selbst gehostete Alternative bevorzugt, für den könnte CrowdSec Manager interessant sein. Dabei handelt es sich um Community-Projekt, das die wichtigsten Funktionen einer Web-UI komplett lokal abbildet.
| Datum | Änderungen |
|---|---|
| 07.06.2026 | Erstellung dieser Anleitung |
1. Grundvoraussetzung
- Docker & Docker Compose v2 (Debian / Ubuntu)
- Traefik ab v3.6 mit CrowdSec installieren und konfigurieren
2. Ordner anlegen
Zuerst legen wir uns passende Ordner-Strukturen an.
mkdir -p /opt/containers/crowdsec-manager/{config,logs,data,backups}
cd /opt/containers/crowdsec-manager
3. Docker Compose anlegen
Nun erstellen wir uns die benötigte Docker Compose Datei:
nano /opt/containers/crowdsec-manager/docker-compose.yml
Inhalt:
services:
crowdsec-manager:
image: hhftechnology/crowdsec-manager:independent
container_name: crowdsec-manager
hostname: crowdsec-ui.euredomain.de
restart: unless-stopped
environment:
- PORT=8080
- ENVIRONMENT=production
- CONFIG_DIR=/app/config
- DATABASE_PATH=/app/data/settings.db
- INCLUDE_CROWDSEC=true
- CROWDSEC_METRICS_URL=http://crowdsec:6060/metrics
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./config:/app/config
- ./logs:/app/logs
- ./data:/app/data
- ./backups:/app/backups
# Optional: GeoIP für Länder-Anzeige im Dashboard
# - ./geoip/GeoLite2-City.mmdb:/app/geoip/GeoLite2-City.mmdb:ro
networks:
- proxy
- crowdsec
labels:
- "traefik.enable=true"
- "traefik.http.routers.crowdsec-ui.entrypoints=websecure"
- "traefik.http.routers.crowdsec-ui.rule=Host(`crowdsec-ui.euredomain.de`)"
- "traefik.http.routers.crowdsec-ui.tls=true"
- "traefik.http.routers.crowdsec-ui.tls.certresolver=http_resolver"
- "traefik.http.routers.crowdsec-ui.service=crowdsec-ui"
- "traefik.http.services.crowdsec-ui.loadbalancer.server.port=8080"
- "traefik.docker.network=proxy"
- "traefik.http.routers.crowdsec-ui.middlewares=default@file,crowdsec-ui-auth"
- "traefik.http.middlewares.crowdsec-ui-auth.basicauth.users=admin:$$apr1$$xxxxxxxx$$xxxxxxxxxxxxxxxxxxxxxx"
networks:
proxy:
external: true
crowdsec:
external: trueCode-Sprache: PHP (php)
4. Konfigurationsskript erstellen
Damit wir die Einstellungen nicht von Hand machen müssen, erstellen wir uns noch ein Konfigurationsskript:
nano /opt/containers/crowdsec-manager/setup.sh
Inhalt:
#!/usr/bin/env bash
set -euo pipefail
# Farben
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# ============================================================
# Vorbedingungen
# ============================================================
if [[ $EUID -ne 0 ]]; then
error "Bitte als root oder mit sudo ausführen."
exit 1
fi
COMPOSE_FILE="./docker-compose.yml"
if [[ ! -f "$COMPOSE_FILE" ]]; then
error "Keine docker-compose.yml im aktuellen Verzeichnis gefunden."
exit 1
fi
if ! command -v htpasswd &>/dev/null; then
warn "htpasswd nicht gefunden. Installiere apache2-utils..."
apt update && apt install -y apache2-utils
fi
# ============================================================
# Benutzereingaben
# ============================================================
echo
info "=== Konfiguration ==="
echo
# Domain
while true; do
read -rp "Domain für das CrowdSec-Manager-UI (z.B. crowdsec-ui.example.de): " DOMAIN
if [[ "$DOMAIN" =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
break
else
warn "Ungültige Domain. Bitte erneut eingeben."
fi
done
# Basic Auth
echo
read -rp "Basic-Auth Benutzername: " BASIC_USER
while [[ -z "$BASIC_USER" ]]; do
warn "Username darf nicht leer sein."
read -rp "Basic-Auth Benutzername: " BASIC_USER
done
while true; do
read -rsp "Basic-Auth Passwort: " BASIC_PASS
echo
read -rsp "Passwort wiederholen: " BASIC_PASS2
echo
if [[ "$BASIC_PASS" == "$BASIC_PASS2" ]] && [[ -n "$BASIC_PASS" ]]; then
break
else
warn "Passwörter stimmen nicht überein oder sind leer."
fi
done
# ============================================================
# Hash generieren und Compose-escapen
# ============================================================
info "Generiere bcrypt-Hash..."
RAW_HASH=$(htpasswd -nbB "$BASIC_USER" "$BASIC_PASS")
ESCAPED_HASH="${RAW_HASH//\$/\$\$}"
success "Hash generiert, \$ für Compose verdoppelt."
# ============================================================
# Backup
# ============================================================
BACKUP="${COMPOSE_FILE}.backup-$(date +%Y%m%d-%H%M%S)"
cp "$COMPOSE_FILE" "$BACKUP"
success "Backup erstellt: $BACKUP"
# ============================================================
# Werte in vorhandener Compose ersetzen
# ============================================================
# Python für sicheres in-place-Editing nutzen (kein sed-Gefummel mit Sonderzeichen)
info "Aktualisiere Werte in docker-compose.yml..."
export NEW_DOMAIN="$DOMAIN"
export NEW_HASH="$ESCAPED_HASH"
export COMPOSE_FILE
python3 <<'PYEOF'
import os
import re
import sys
compose_file = os.environ["COMPOSE_FILE"]
new_domain = os.environ["NEW_DOMAIN"]
new_hash = os.environ["NEW_HASH"]
with open(compose_file, "r", encoding="utf-8") as f:
content = f.read()
original = content
changes = []
# --- 1) Host(`...`) Regel ersetzen ---
host_pattern = re.compile(r"(Host\(`)[^`]+(`\))")
new_content, n_host = host_pattern.subn(rf"\g<1>{new_domain}\g<2>", content)
if n_host > 0:
changes.append(f"Host()-Regel: {n_host}x ersetzt")
content = new_content
else:
print("WARN: Keine Host(`...`)-Regel gefunden — bitte manuell prüfen.", file=sys.stderr)
# --- 2) hostname-Feld ersetzen (falls vorhanden) ---
hostname_pattern = re.compile(r"^(\s*hostname:\s*).+$", re.MULTILINE)
new_content, n_hn = hostname_pattern.subn(rf"\g<1>{new_domain}", content)
if n_hn > 0:
changes.append(f"hostname:-Feld: {n_hn}x ersetzt")
content = new_content
# --- 3) basicauth.users=... ersetzen ---
# Matcht die ganze Zeile mit basicauth.users= bis zum Ende (vor schließendem " falls vorhanden)
auth_pattern = re.compile(r'(basicauth\.users=)[^"\n]*')
new_content, n_auth = auth_pattern.subn(lambda m: m.group(1) + new_hash, content)
if n_auth > 0:
changes.append(f"basicauth.users: {n_auth}x ersetzt")
content = new_content
else:
print("WARN: Keine basicauth.users-Zeile gefunden — Basic Auth muss bereits in der Compose angelegt sein.", file=sys.stderr)
sys.exit(2)
if content == original:
print("WARN: Keine Änderungen vorgenommen.", file=sys.stderr)
sys.exit(3)
with open(compose_file, "w", encoding="utf-8") as f:
f.write(content)
for c in changes:
print(f" - {c}")
PYEOF
PY_RC=$?
if [[ $PY_RC -ne 0 ]]; then
error "Ersetzen fehlgeschlagen. Backup wiederherstellen mit:"
error " cp $BACKUP $COMPOSE_FILE"
exit 1
fi
success "Werte aktualisiert."
# ============================================================
# Validierung
# ============================================================
info "Validiere Compose-Datei..."
if ! docker compose config &>/dev/null; then
error "Compose-Datei hat Syntaxfehler!"
docker compose config || true
error "Backup wiederherstellen: cp $BACKUP $COMPOSE_FILE"
exit 1
fi
success "Syntax ok."
# ============================================================
# Abschluss
# ============================================================
echo
success "Fertig!"
echo
info "UI: https://${DOMAIN}"
info "Login: ${BASIC_USER} / (dein Passwort)"
echoCode-Sprache: PHP (php)
Nun starten wir das Skript:
cd /opt/containers/crowdsec-manager/
chmod +x setup.sh
./setup.sh
5. CrowdSec Manager starten
Nun starten wir CrowdSec Manager mit folgendem Befehl:
docker compose -f /opt/containers/crowdsec-manager/docker-compose.yml up -d
6. CrowdSec Manager
Geht nun auf eure Domain und loggt euch mit den Anmeldedaten aus Schritt 4 ein. Anschließend seht ihr folgendes:


Hier könnt ihr nun auch IPs entsperren, Regeln anpassen etc.
CrowdSec Manager hat Schreibrechte auf CrowdSec. Dies sollte jedem aus dem Aspekt der Sicherheit klar sein.

Schöne Sache, hab sie mir gerade auch auf dem Server gepackt und Authentik vorgeschaltet (ist bei mir eh fester Bestandteil meines Setup).
Das handling zum nachschauen was da so gerade los ist, war mir persönlich mit dem cscli Befehl umständlich gewesen. Man hat ja nicht immer den kompletten Syntax im Kopf. 😉
So ist das aber eine schöne Sache und klasse aufbereitet.
Danke für die gute Idee!
Ich habe bei mir die Absicherung über Authelia eingerichtet, so braucht es die setup.sh und das HTTP-Passwort und basicauth (htpasswd) nicht. Dafür einfach gemäss Anleitung bei
traefik.http.routers.crowdsec-ui.middlewares=default@file, middlewares-authelia@file
die Middleware für Authelia anhängen und in Authelia in der configuration.yml den Eintrag
access_control:
default_policy: deny
rules:
– domain: crowdsec-iu.euredomain.de
policy: one_factor
hinzufügen sowie Authelia neu starten (down + up -d). Hier noch die Links zu Autelia:
https://goneuland.de/authelia-zweifaktor-authentifizierung-mittels-docker-compose-und-traefik-installieren/
https://goneuland.de/verschiedene-dienste-absichern-mittels-authelia/
Bin gespannt, ob das Dashboard dann auch erreichbar ist, wenn ich von einem Rechner/Netzwerk wegen einem Fehler gebannt werde!
ggf. darüber nachgedacht dies nicht nur mit htaccess zu machen sondern ggf. mit Authentik als forward proxy ? würde doch auch gehen 🙂
so habe ich das bei mir laufen
Vielen Dank für die Anleitung, funktioniert soweit – in der Health & Diag… Anzeige steht im Abschnitt “Connected Bouncers” der Firewall@<IP> auf stale – soll das so sein?
Vielen Dank für deine Arbeit. Sowas hab ich gesucht.😎