SSH-Brute-Force-Attacken laufen auf jedem öffentlichen Server im Dauerbetrieb. Der Großteil der Linux-Server setzt standardmäßig auf Passwörter oder statische SSH-Keys – beides hat bekannte Schwächen. Passwörter sind durch Credential-Stuffing und GPU-Cluster in Minuten geknackt, statische Keys bleiben unverändert, bis jemand sie manuell rotiert.
Dieser Artikel zeigt, wie du auf Ubuntu 24.04 LTS und Ubuntu 26.04 LTS deinen Server mit zwei konkreten Maßnahmen absicherst: SSH mit FIDO2 und YubiKey für hardwarebasierte, passwortlose Authentifizierung und CrowdSec als kollaboratives Intrusion-Prevention-System. Beides zusammen schließt die häufigsten Angriffsvektoren, ohne dass du dich um Passwort-Rotation oder Key-Management kümmern musst.
Die Konfiguration funktioniert auf einem frischen Server genauso wie auf einem bestehenden Setup – wir machen keine Annahmen über den aktuellen Zustand.
💡 Voraussetzungen: Grundkenntnisse in
SSHundsystemd. Ein Ubuntu 24.04 oder 26.04 Server mit Root- oder sudo-Zugang. Für FIDO2 benötigst du einen YubiKey (oder ein anderes FIDO2-Gerät). Für CrowdSec reicht ein TCP-Port für SSH. Der Artikel ist für Linux-Administratoren, DevOps Engineers und Homelab-Betreiber, die bestehende Server absichern oder neue nach aktuellem Standard aufsetzen möchten.
⚠️ Hinweis: Dieser Artikel behandelt Serverhärtung auf Betriebssystem-Ebene. Anwendungsspezifische Härtung (Webserver, Datenbanken) bleibt außen vor, da jede Anwendung eigene Anforderungen hat.
SSH mit FIDO2 & YubiKey
Warum FIDO2 für SSH?
Passwörter sind das schwächste Glied in der SSH-Kette. Jeder Server mit öffentlicher IP wird permanent von Brute-Force-Scannern bearbeitet – fail2ban-Logs zeigen das täglich. Statische SSH-Keys (Ed25519, RSA) sind besser, aber sie rotieren nicht. Wer einen kompromittierten Private Key hat, bleibt unbemerkt aktuell.
FIDO2 löst beide Probleme:
Der Private Key wird auf einem physischen Gerät gespeichert (YubiKey) und verlässt dieses nie. Ohne den YubiKey am lokalen Rechner ist kein Login möglich – egal wie gut Passwort oder Key sind. Zusätzlich erzwingt FIDO2 eine Benutzer-Verifikation (PIN oder Biometrie) bei jedem Login.
Was sich ändert:
| Eigenschaft | Passwort | Statischer SSH-Key | FIDO2 + YubiKey |
|---|---|---|---|
| Physische Komponente | Nein | Nein | Ja |
| Brute-Force-resistent | Bedingt | Ja | Ja |
| Phishing-resistent | Nein | Bedingt | Ja |
| Rotation | Manuell | Manuell | Automatisch (Key auf Gerät) |
| Multi-Factor | Optional | Nein | Standard |
Die Kombination aus physischem Token und PIN macht SSH-Logins nahezu unangreifbar. Ein Angreifer bräuchte sowohl den YubiKey als auch den PIN – und beides an derselben physischen Station.
💡 FIDO2 vs. U2F: FIDO2 ist der Nachfolger von U2F und unterstützt WebAuthn (Browser) und CTAP2 (native Apps). Für SSH ist CTAP2 relevant – der YubiKey wird direkt vom SSH-Client angesprochen, ohne Browser-Umweg.
FIDO2 & WebAuthn: Wie es funktioniert
FIDO2 arbeitet Challenge-Response-basiert. Der Server sendet eine zufällige Challenge, der YubiKey signiert sie mit einem auf dem Gerät gespeicherten privaten Schlüssel, und der Server verifiziert die Signatur. Der private Schlüssel verlässt nie den YubiKey.
┌─────────────────────────────────────────────────────────────┐
│ FIDO2 Challenge-Response │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ SSH-Client │ │ YubiKey │ │ SSH-Server │ │
│ └──────┬───────┘ └────┬─────┘ └──────┬───────┘ │
│ │ │ │ │
│ │ Challenge (random) │ │ │
│ │ ──────────────────>│ │ │
│ │ │ PIN / Touch │ │
│ │ │ ────────── (User) │ │
│ │ │ │ │
│ │ Signature(Chall.) │ │ │
│ │ <───────────────── │ │ │
│ │ │ │ │
│ │ Challenge + Signat.│ │ │
│ │ ──────────────────────────────────────>│ │
│ │ │ │ │
│ │ │ Verify(PubKey,Sig)│ │
│ │ │ ───────────────── │ │
│ │ │ │ │
│ │ Access Granted │ │ │
│ │ <──────────────────────────────────────│ │
│ │
└─────────────────────────────────────────────────────────────┘
Was passiert im Detail:
- Der SSH-Client erkennt, dass ein FIDO2-Key konfiguriert ist
- Er fordert den YubiKey zur Signierung der Challenge auf
- Der Nutzer gibt seinen PIN ein oder berührt den Sensor
- Der YubiKey signiert die Challenge mit dem ed25519-sk Schlüsselpaar
- Der Server verifiziert die Signatur gegen den gespeicherten öffentlichen Schlüssel
- Der Login wird gewährt
Der entscheidende Unterschied zu statischen Keys: Die Challenge ist bei jedem Login-Versuch anders. Selbst wenn jemand die Signatur abfängt, kann er sie nicht wiederverwenden (Replay-Schutz).
YubiKey Hardware-Setup
Für SSH mit FIDO2 eignen sich alle YubiKeys der 5er-Serie mit USB-C oder USB-A. Wichtig ist, dass der Key FIDO2 und nicht nur U2F unterstützt – das erkennst du an der Produktbezeichnung "YubiKey 5" (nicht "YubiKey Security Key", der nur U2F kann).
YubiKey initialisieren:
# YubiKey Manager installieren (Debian/Ubuntu)
sudo apt install yubikey-manager -y
# FIDO2-PIN setzen (Standard-PIN ist leer)
ykman fido access change-pin
Der PIN schützt den YubiKey vor unbefugter Nutzung. Wähl einen PIN mit mindestens 6 Zeichen. Ohne PIN kann jeder mit physischem Zugriff zum YubiKey die FIDO2-Keys auslesen und nutzen.
Verifikation:
# Prüfe ob der YubiKey erkannt wird
ykman fido info
Erwartete Ausgabe:
Applications: FIDO2
slots: 2
pin: true
pin retries: 3
⚠️ Backup-YubiKey & Break-Glass-Notfallanker: Erstelle immer mindestens einen zweiten YubiKey als physisches Backup an einem sicheren Ort. Wenn du deinen einzigen YubiKey verlierst, bist du ausgesperrt – es gibt keinen Reset-Mechanismus für FIDO2-Keys. Für Root- und Cloud-Server empfiehlt sich zudem die Absicherung der Out-of-Band-Webkonsole des Hosters (z. B. Hetzner Robot, Proxmox NoVNC oder IPMI) mit separatem 2FA als Notfallanker.
SSH-Schlüssel mit FIDO2 erzeugen
OpenSSH 8.2+ unterstützt FIDO2-Schlüssel nativ. Der Befehl ssh-keygen erzeugt mit der Option -t ed25519-sk einen Schlüssel, der auf dem YubiKey gespeichert wird.
Schlüssel erzeugen:
# FIDO2-SSH-Schlüssel erzeugen
ssh-keygen -t ed25519-sk -C "admin@server"
Während der Erzeugung wirst du aufgefordert, den YubiKey zu berühren (Touch) und deinen FIDO2-PIN einzugeben. Der private Schlüssel wird auf dem YubiKey gespeichert, nicht auf der Festplatte.
Was passiert bei der Erzeugung:
ssh-keygenerzeugt ein ed25519-sk Schlüsselpaar- Der private Schlüssel wird im Secure Element des YubiKeys abgelegt
- Der öffentliche Schlüssel wird in
~/.ssh/id_ed25519-sk.pubgespeichert - Für jeden Login-Versuch wird der YubiKey benötigt
Optionen:
# Ohne Touch (nur PIN)
ssh-keygen -t ed25519-sk -O no-touch-required
# Mit benutzerdefiniertem Dateinamen
ssh-keygen -t ed25519-sk -f ~/.ssh/id_yubikey -C "admin@server"
💡 Resident vs. Non-Resident Keys: Mit
-O residentwird der private Schlüssel vollständig auf dem YubiKey gespeichert und kann von dort exportiert werden. Ohne diese Option wird nur ein Verweis gespeichert. Für SSH empfiehlt sich Non-Resident (Standard), da der Schlüssel dann nicht exportierbar ist.
Öffentlichen Schlüssel auf den Server kopieren:
# Öffentlichen Schlüssel auf den Server übertragen
ssh-copy-id -i ~/.ssh/id_ed25519-sk.pub user@server
Falls ssh-copy-id mit FIDO2 Probleme macht (manchmal der Fall bei älteren OpenSSH-Versionen), kopiere den Schlüssel manuell:
# Manuelle Installation
cat ~/.ssh/id_ed25519-sk.pub | ssh user@server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
Überprüfung auf dem Server:
# Prüfe den installierten Schlüssel
cat ~/.ssh/authorized_keys | grep "sk@"
Die Zeile sollte mit ssh-ed25519-cert-v01@openssh.com beginnen oder sk@ enthalten – das bestätigt, dass es sich um einen FIDO2-Schlüssel handelt.
SSH-Server für FIDO2 konfigurieren
Der SSH-Server muss wissen, dass er FIDO2-Schlüssel akzeptiert. In der Standardkonfiguration ist das bereits der Fall, aber wir wollen explizit sein und zusätzliche Sicherheitseinstellungen setzen.
Server-Konfiguration:
sudo nano /etc/ssh/sshd_config
Relevante Einstellungen:
# FIDO2-Schlüssel erlauben (Standard: yes)
PubkeyAuthentication yes
# Nur SSH-Keys erlauben (Passwörter deaktivieren)
PasswordAuthentication no
# Root-Login verbieten
PermitRootLogin no
# Maximal 3 Authentifizierungsversuche
MaxAuthTries 3
# Grace Time auf 30 Sekunden begrenzen
LoginGraceTime 30
# Information Disclosure minimieren
DebianBanner no
Banner none
# Keepalive für Long-Running Sessions
ClientAliveInterval 300
ClientAliveCountMax 2
Optional: Nur FIDO2-Keys erlauben (keine statischen Keys mehr):
Wenn du ausschließlich FIDO2-Key verwenden möchtest, kannst du die PubkeyAuthOptions einschränken:
# Nur verifizierte FIDO2-Keys akzeptieren
PubkeyAuthOptions verify-required
⚠️ Vorsicht mit
verify-required: Diese Option akzeptiert nur FIDO2-Keys mit Benutzer-Verifikation (PIN oder Biometrie). Statische Ed25519- und RSA-Keys werden abgelehnt. Teste die Änderung zuerst in einer zusätzlichen Session, bevor du die bestehende schließt.
SSH-Server neu starten:
sudo systemctl restart sshd
⚠️ Nie die aktive Session schließen, bevor du getestet hast! Öffne ein zweites Terminal und teste den Login mit dem neuen FIDO2-Key. Erst wenn der Login funktioniert, schließt du die alte Session.
Testen:
# Login mit FIDO2-Key
ssh -i ~/.ssh/id_ed25519-sk user@server
# Prüfe, welche Methode verwendet wurde
ssh -v user@server 2>&1 | grep "Offering"
Die Verbose-Ausgabe sollte Offering public key: .../id_ed25519-sk zeigen und Accepted publickey als Ergebnis.
Zertifikatsbasierte Authentifizierung
Für größere Umgebungen lohnt sich eine SSH Certificate Authority (CA). Statt einzelne Keys auf jedem Server zu verteilen, signiert eine CA öffentliche Schlüssel und der Server vertraut nur der CA.
CA auf dem signierenden Host erstellen:
# CA-Schlüsselpaar erzeugen
sudo mkdir -p /etc/ssh/ca
sudo ssh-keygen -t ed25519 -f /etc/ssh/ca/server-ca -C "SSH CA"
Öffentlichen CA-Schlüssel auf allen Servern verteilen:
# Auf jedem Server: CA Public Key als TrustedKey eintragen
echo "cert-authority $(cat /etc/ssh/ca/server-ca.pub)" >> /etc/ssh/authorized_keys.d/trusted-ca
Benutzer-Key mit CA signieren:
# FIDO2-Key signieren (erzeugt ein Zertifikat)
sudo ssh-keygen -s /etc/ssh/ca/server-ca \
-I "admin-fido2" \
-n admin \
-V +52w \
~/.ssh/id_ed25519-sk.pub
Optionen:
| Option | Bedeutung |
|---|---|
-s |
CA-Private Key zum Signieren |
-I |
Zertifikat-ID (für Logs) |
-n |
Erlaubte Usernames (kommagetrennt) |
-V |
Gültigkeitsdauer (+52w = 52 Wochen) |
Das erzeugte Zertifikat (id_ed25519-sk-cert.pub) wird vom SSH-Client automatisch erkannt und beim Login mitgeschickt.
💡 Vorteil der CA: Du musst auf jedem Server nur den öffentlichen CA-Schlüssel hinterlegen. Individuelle Benutzer-Keys werden zentral verwaltet und können über die CA widerrufen werden, ohne dass du auf jedem Server die
authorized_keyseditierst.
SSH-Config & ssh-agent
Für den täglichen Gebrauch lohnt sich eine saubere SSH-Konfiguration auf dem Client.
~/.ssh/config:
# Server-Definition mit FIDO2-Key
Host server
HostName 192.168.1.100
User admin
IdentityFile ~/.ssh/id_ed25519-sk
IdentitiesOnly yes
# Jump-Host für interne Server
Host internal-server
HostName 10.0.0.50
User admin
ProxyJump server
IdentityFile ~/.ssh/id_ed25519-sk
IdentitiesOnly yes
IdentitiesOnly yes zwingt den SSH-Client, nur den angegebenen Key zu verwenden. Das verhindert, dass versehentlich ein anderer Key aus dem Agent angeboten wird.
ssh-agent mit FIDO2:
Der ssh-agent kann FIDO2-Keys unterstützen, aber da der private Schlüssel auf dem YubiKey bleibt, wird nur der Verweis im Agent gespeichert. Für FIDO2 macht der Agent wenig Sinn – der YubiKey muss sowieso physisch angeschlossen sein.
# ssh-agent starten (optional für FIDO2)
eval $(ssh-agent -s)
# Key hinzufügen (Touch wird bei jedem Login angefordert)
ssh-add ~/.ssh/id_ed25519-sk
💡 Empfehlung: Für FIDO2-Keys den ssh-agent weglassen. Der Agent bringt keinen Vorteil, da der YubiKey bei jedem Login geberührt werden muss. Stattdessen die
IdentityFilein~/.ssh/confignutzen.
Troubleshooting: Häufige SSH-FIDO2-Probleme
Problem: "Key rejected by server"
# Prüfe ob FIDO2 im SSH-Server unterstützt wird
ssh -V # OpenSSH_8.9 oder höher
# Prüfe die Server-Konfiguration
sudo sshd -T | grep pubkeyauth
⚠️ **Ursache: OpenSSH-Version zu alt (unter 8.2). FIDO2-Support kam mit OpenSSH 8.2. Ubuntu 24.04 hat OpenSSH 9.6, Ubuntu 26.04 hat OpenSSH 9.7 – beide unterstützen FIDO2.
Problem: "Sign_and_send_pubkey: signing failed: agent refused operation"
# ssh-agent läuft und versucht den Key zu signieren
# Lösung: Key explizit angeben oder Agent deaktivieren
ssh -i ~/.ssh/id_ed25519-sk user@server
Ursache: Der ssh-agent versucht, den Key mit einer alten Methode zu signieren. IdentitiesOnly yes in der SSH-Config löst das Problem.
Problem: "Touch your authenticator" erscheint nicht
# Prüfe ob der YubiKey erkannt wird
lsusb | grep Yubico
# Prüfe ob das FIDO2-Modul geladen ist
ls /dev/hidraw*
Ursache: YubiKey nicht erkannt oder Treiber fehlen. Unter Ubuntu sind alle nötigen Treiber standardmäßig vorhanden.
Problem: "PIN auth required" aber PIN wird nicht abgefragt
# FIDO2-PIN manuell setzen oder zurücksetzen
ykman fido access change-pin
⚠️ Ursache: Der YubiKey wurde ohne PIN initialisiert oder der PIN ist veraltet.
Verifikation auf dem Server:
# Logs prüfen
journalctl -u sshd -f
# Erfolgreiche FIDO2-Logins erkennen
journalctl -u sshd | grep "Accepted publickey"
Die Logs zeigen, welcher Key verwendet wurde und ob es ein FIDO2-Key (sk@) war.
CrowdSec: Kollaborativer Intrusion-Prevention
Fail2Ban vs. CrowdSec
Fail2Ban liest lokalen Log-Dateien und bannt IPs über iptables/nftables-Regeln. Jeder Server agiert isoliert – ein Angreifer, der von 1.000 IPs scannt, muss auf jedem Server einzeln gebannt werden.
CrowdSec geht anders vor: Der Agent analysiert Logs und Entscheidungen (Decisions) werden zentral in einer Community-Datenbank geteilt. Wenn ein Angreifer auf einem Community-Server gebannt wird, profitieren alle anderen davon. Zusätzlich trennt CrowdSec die Analyse (Agent) von der Durchsetzung (Bouncer), was die Architektur flexibler macht.
Was sich ändert:
| Eigenschaft | Fail2Ban | CrowdSec |
|---|---|---|
| Log-Analyse | Lokal | Lokal + Community |
| Durchsetzung | iptables direkt | Bouncer (extern) |
| Performance | Guter bei wenigen Jails | Skalierbar für viele Server |
| Community | Keine | Geteilte Decisions |
| Dashboard | Kein | Web-Dashboard + Metriken |
| Architektur | Monolith | Agent + Bouncer getrennt |
💡 Kernidee: CrowdSec sammelt Signaturen von Angriffsmustern (Scenarios) und vergleicht sie mit deinen Logs. Treffer führen zu Decisions (Bans), die von Bouncers durchgesetzt werden. Die Community-Intelligence ergänzt lokale Erkennung.
Installation & Ersteinrichtung
CrowdSec Agent installieren:
# Repository hinzufügen (Debian/Ubuntu)
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
# CrowdSec installieren
sudo apt install crowdsec -y
Firewall-Bouncer installieren:
# Bouncer für nftables/iptables
sudo apt install crowdsec-firewall-bouncer-nftables -y
⚠️ Reihenfolge beachten: Zuerst den Agent installieren, dann den Bouncer. Der Bouncer verbindet sich mit dem Agenten über die lokale API. Starte den Bouncer nicht, bevor der Agent läuft.
Status prüfen:
sudo systemctl status crowdsec
sudo systemctl status crowdsec-firewall-bouncer
Erwartete Ausgabe für den Agenten:
● crowdsec.service - CrowdSec SIEM
Loaded: loaded (/lib/systemd/system/crowdsec.service; enabled)
Active: active (running) since Mon 2026-08-26 12:00:00 UTC
Erste Decisions prüfen:
sudo cscli decisions list
Im Anfangszustand ist die Liste leer. Sobald der Agent Logs analysiert und Angriffsmuster erkennt, erscheinen hier Decisions.
Szenarien & Entscheidungen
CrowdSec arbeitet mit Scenarios – das sind Signaturen, die bestimmte Angriffsmuster beschreiben. Der Agent vergleicht Log-Einträge mit diesen Scenarios und erstellt Decisions bei Treffern.
Standardmäßig aktivierte Scenarios:
sudo cscli scenarios list
| Name | Service | Type |
|---|---|---|
ssh-bf |
ssh | leak |
ssh-slow-bf |
ssh | leak |
http-bad-user-agent |
http | leak |
http-dos-bf-path |
http | leak |
Für SSH relevante Scenarios:
| Scenario | Erkennung | Aktion |
|---|---|---|
ssh-bf |
5 fehlgeschlagene Logins in 10 Min | Ban für 1h |
ssh-slow-bf |
Langsamer Brute-Force über Stunden | Ban für 24h |
ssh-crawl |
Verhaltensanalyse von Login-Versuchen | Ban für 4h |
Eigene Scenarios erstellen:
Falls die Standard-Scenarios nicht passen, kannst du eigene definieren:
sudo nano /etc/crowdsec/scenarios/custom-ssh-aggressive.yaml
type: leak
name: ssh-aggressive-scan
description: "Aggressive SSH-Scans mit vielen fehlgeschlagenen Logins"
labels:
service: ssh
remediation: true
filter:
LABELS.service == "ssh" && MSG.ContainerName == "sshd"
groupby: LABELS.source_ip
capacity: 5
leakspeed: 1m
blackhole: 1m
certainty: 75
for: 10m
Verifikation:
# Scenarios neu laden
sudo cscli scenarios list | grep custom
# Testszenario simulieren
sudo cscli scenarios install --local /etc/crowdsec/scenarios/custom-ssh-aggressive.yaml
Bouncers (Firewall-Integration)
Bouncers setzen die Decisions des Agents durch. Es gibt verschiedene Bouncer-Typen für unterschiedliche Anwendungsfälle.
nftables-Bouncer (Standard):
# Prüfe ob der Bouncer aktiv ist
sudo cscli bouncers list
Name │ IP Address │ Valid Until │ Type
───────────┼────────────┼─────────────┼──────
firewall │ 127.0.0.1 │ 2027-01-01 │ nftables
Bouncer-Typen:
| Bouncer | Einsatz | Vorteil |
|---|---|---|
nftables |
Server-Firewall | Schnell, kernel-nah |
nginx |
Webserver-Inline | Kein extra Port |
caddy |
Caddy-Integration | Einfache Konfiguration |
crowdsec-openapi |
Externe APIs | Flexibel |
Firewall-Regeln prüfen:
# CrowdSec-Chain in nftables prüfen
sudo nft list chain inet crowdsec-crowdsec-block
table inet crowdsec-crowdsec {
chain crowdsec-block {
type filter hook input priority filter; policy accept;
ip saddr @crowdsec-blacklist-jump drop
ip6 saddr @crowdsec-blacklist6-jump drop
}
}
Whitelisting
Nicht jede IP soll gebannt werden. Lokale Server, Monitoring-Systeme oder vertrauenswürdige Netzwerke brauchen Whitelisting.
Whitelist-Datei:
sudo nano /etc/crowdsec/parsers/s02-enrich/whitelist.yaml
filter: "Meta.IP != ''"
whitelist:
reason: "Lokale Adresse"
expression: "Ip('127.0.0.1') || Ip('::1')"
expression: "Ip('192.168.0.0/16')"
expression: "Ip('10.0.0.0/8')"
Alternativ: Whitelist über cscli:
# Einzelne IP whitelishten
sudo cscli decisions add --ip 192.168.1.100 --duration 0 --type whitelist --reason "Monitoring"
# IP-Range whitelishten
sudo cscli decisions add --range 192.168.1.0/24 --duration 0 --type whitelist --reason "Lokales Netz"
⚠️ Whitelist vor Bans prüfen: Prüfe immer, ob eine IP möglicherweise aus einem lokalen Monitoring-System oder einem vertrauenswürdigen Netzwerk kommt, bevor du Banns analysierst. Sonst bannt CrowdSec versehentlich eigene Systeme.
Dashboards & Alerting
CrowdSec bietet ein Web-Dashboard und Metriken für Monitoring-Systeme.
Web-Dashboard:
# Dashboard-Zugangsdaten anzeigen
sudo cscli console status
Das Dashboard ist auf http://localhost:6060 erreichbar (Standard-Port). Für externe Zugriffe die Firewall anpassen.
Metriken für Prometheus:
# Metriken-Endpunkt prüfen
curl -s http://localhost:6060/metrics | head -20
# Wichtige Metriken
crowdsec_decisions_total{source_ip="...",scenario="..."}
crowdsec_alerts_total{scenario="..."}
crowdsec_bouncers_total{type="..."}
Alerting über Webhook:
sudo nano /etc/crowdsec/notifications/email.yaml
type: email
name: email_default
log_level: info
# Email-Konfiguration
smtp_host: "smtp.example.com"
smtp_port: 587
smtp_username: "alert@example.com"
smtp_password: "DEIN_PASSWORT"
smtp_from: "crowdsec@example.com"
smtp_to:
- "admin@example.com"
smtp_subject: "CrowdSec Alert: {scenario}"
format: |
CrowdSec hat einen Angriff erkannt.
Scenario: {scenario}
Source IP: {source_ip}
Entscheidung: {decision}
Zeit: {created_at}
Verifikation:
# Notifications prüfen
sudo cscli notifications list
# Test-Benachrichtigung senden
sudo cscli alert inspect --id 1
Performance & Skalierung
Für größere Umgebungen (mehr als 10 Server) lohnt sich eine zentrale CrowdSec-Instanz.
Architektur für mehrere Server:
┌─────────────────────────────────────────────────────────────┐
│ CrowdSec Multi-Server Architektur │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Server1 │ │ Server2 │ │ Server3 │ │
│ │ Agent │ │ Agent │ │ Agent │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Lokale API (zentral) │ │
│ │ http://central:8080 │ │
│ └──────────────────────────┬──────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Community Database │ │
│ │ (geteilte Decisions) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Lokalen Agent auf zentrale API zeigen:
sudo nano /etc/crowdsec/config.yaml
api:
client:
insecure_skip_verify: false
server:
listen_uri: http://127.0.0.1:8080
💡 Performance: Ein einzelner CrowdSec-Agent kann problemlos 10.000+ Log-Einträge pro Sekunde verarbeiten. Für die meisten Setups reicht ein zentraler Agent auf einem dedizierten Server.
Troubleshooting
Problem: "crowdsec service not running"
# Logs prüfen
sudo journalctl -u crowdsec -f
# Häufige Ursache: Port 8080 belegt
sudo ss -tulnp | grep 8080
Problem: "Bouncer nicht verbunden"
# Bouncer-API-Token prüfen
sudo cscli bouncers list
# Token neu generieren
sudo cscli bouncers add my-bouncer --auto
Problem: Eigene IPs werden gebannt
# Whitelist prüfen
sudo cscli decisions list --type whitelist
# Lokale IP zur Whitelist hinzufügen
sudo cscli decisions add --ip 192.168.1.100 --duration 0 --type whitelist --reason "Eigener Server"
Verifikation:
# Aktive Decisions prüfen
sudo cscli decisions list
# Logs des Agenten prüfen
sudo journalctl -u crowdsec | grep "decision"
System-Härtung
Kernel-Parameter (sysctl)
Der Linux-Kernel bietet über sysctl Dutzende von Parametern, die Angriffsfläche minimieren. Die Standardeinstellungen sind auf Kompatibilität optimiert, nicht auf Sicherheit.
Datei erstellen:
sudo nano /etc/sysctl.d/99-hardening.conf
Netzwerk-Sicherheit:
# IP-Spoofing verhindern
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# ICMP-Redirects deaktivieren
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
# Source-Routing deaktivieren
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0
# SYN-Flood-Schutz aktivieren
net.ipv4.tcp_syncookies = 1
# Log Martian-Pakete
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
Speicher-Schutz:
# Address Space Layout Randomization
kernel.randomize_va_space = 2
# Dmesg einschränken (nur für Root)
kernel.dmesg_restrict = 1
# Kernel-Pointer verbergen
kernel.kptr_restrict = 2
# BPF einschränken
kernel.unprivileged_bpf_disabled = 1
# User namespaces einschränken (für Container)
kernel.unprivileged_userns_clone = 0
Kernel-Laden verhindern:
# Module nicht mehr laden können (nach Boot)
kernel.modules_disabled = 1
⚠️
kernel.modules_disabled = 1verhindert das Laden neuer Kernel-Module nach dem Boot. Das kann etwa NVIDIA-Treiber oder VirtualBox-Module brechen. Nur aktivieren, wenn du sicher bist, dass alle nötigen Module beim Boot geladen werden.
Parameter laden:
sudo sysctl -p /etc/sysctl.d/99-hardening.conf
Verifikation:
# Prüfe ob Parameter gesetzt sind
sysctl net.ipv4.conf.all.rp_filter
sysctl kernel.randomize_va_space
Erwartete Ausgabe:
net.ipv4.conf.all.rp_filter = 1
kernel.randomize_va_space = 2
Filesystem-Hardening
Standardmäßig haben viele Dateien und Verzeichnisse zu weite Berechtigungen. Besonders /tmp, /var/tmp und /dev/shm sind Angriffsflächen.
Mount-Optionen für /tmp:
# /tmp ist oft bereits ein tmpfs, prüfe mit:
mount | grep tmp
Falls /tmp auf der Hauptfestplatte liegt, empfiehlt sich eine dedizierte Partition mit sicheren Mount-Optionen:
# In /etc/fstab eintragen (bei separater /tmp-Partition)
# UUID=... /tmp ext4 defaults,noexec,nosuid,nodev 0 2
Verzeichnisrechte strikt setzen:
# Kritische Verzeichnisse absichern
sudo chmod 700 /root
sudo chmod 600 /etc/shadow
sudo chmod 600 /etc/gshadow
sudo chmod 644 /etc/passwd
sudo chmod 644 /etc/group
# SSH-Verzeichnis
sudo chmod 700 /root/.ssh
sudo chmod 600 /root/.ssh/authorized_keys
SUID/SGID-Bits reduzieren:
# SUID-Bits auf Standard-Tools prüfen
find / -perm -4000 -type f 2>/dev/null
Unnötige SUID-Bits entfernen (Beispiele):
# Nur wenn die Programme nicht als SUID benötigt werden
sudo chmod u-s /usr/bin/newgrp
sudo chmod u-s /usr/bin/chsh
sudo chmod u-s /usr/bin/chfn
💡 Tipp:
find / -perm -4000 -type fzeigt alle Programme mit SUID-Bit. Nicht blind entfernen – manche Programme brauchen das Bit für ihre Funktion (z. B.sudo,passwd).
Service-Minimierung
Jeder laufende Dienst ist eine potenzielle Angriffsfläche. Auf einem frischen Ubuntu-Server laufen Dutzende Services, die du möglicherweise nicht brauchst.
Aktive Services prüfen:
# Alle laufenden Services anzeigen
systemctl list-units --type=service --state=running
# Häufig unnötige Services auf einem Server
systemctl list-units --type=service --state=running | grep -E "cups|bluetooth|avahi|modem"
Unnötige Services deaktivieren:
# Drucker-Service (auf einem Server nicht benötigt)
sudo systemctl stop cups
sudo systemctl disable cups
sudo systemctl mask cups
# Bluetooth (auf einem Server nicht benötigt)
sudo systemctl stop bluetooth
sudo systemctl disable bluetooth
sudo systemctl mask bluetooth
# mDNS/Avahi (Service Discovery – auf einem Server unnötig)
sudo systemctl stop avahi-daemon
sudo systemctl disable avahi-daemon
sudo systemctl mask avahi-daemon
mask vs. disable:
| Aktion | Effekt |
|---|---|
disable |
Service startet nicht automatisch, kann aber manuell gestartet werden |
mask |
Service kann überhaupt nicht gestartet werden (Symlink auf /dev/null) |
⚠️ Nur masken, was du wirklich nicht brauchst. Falls du später doch einen Service brauchst, musst du erst
systemctl unmaskausführen.
Systemd-Security-Score prüfen:
# Security Score für einen Service anzeigen
systemd-analyze security sshd.service
→ Overall exposure level for sshd.service: 9.6 OK
Je niedriger der Wert, desto besser. sshd hat normalerweise einen guten Score, wenn PasswordAuthentication no und PermitRootLogin no gesetzt sind.
Audit-Logging
auditd protokolliert sicherheitsrelevante Ereignisse und speichert sie in einer manipulationsresistenten Log-Datei. Das ist die Grundlage für Forensik und Compliance.
Installation:
sudo apt install auditd -y
sudo systemctl enable auditd
Regeln für SSH und sudo:
# SSH-Konfiguration überwachen
sudo auditctl -w /etc/ssh/sshd_config -p wa -k sshd_config
# Authorized-Keys überwachen
sudo auditctl -w /root/.ssh/authorized_keys -p wa -k ssh_keys
# sudo-Konfiguration überwachen
sudo auditctl -w /etc/sudoers -p wa -k sudoers
sudo auditctl -w /etc/sudoers.d/ -p wa -k sudoers
# Crontab überwachen
sudo auditctl -w /etc/crontab -p wa -k cron
sudo auditctl -w /var/spool/cron/ -p wa -k cron
Regeln dauerhaft speichern:
sudo nano /etc/audit/rules.d/hardening.rules
# SSH
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /root/.ssh/authorized_keys -p wa -k ssh_keys
# Sudo
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
# Cron
-w /etc/crontab -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
# Benutzeränderungen
-w /etc/passwd -p wa -k user_change
-w /etc/shadow -p wa -k user_change
-w /etc/group -p wa -k group_change
Verifikation:
# Regeln anzeigen
sudo auditctl -l
# Audits für einen Key suchen
sudo ausearch -k sshd_config --interpret
# Audit-Report erstellen
sudo aureport --auth
File-Integrity-Monitoring (AIDE)
AIDE (Advanced Intrusion Detection Environment) vergleicht Dateien mit einem bekannten Zustand und meldet Änderungen.
Installation:
sudo apt install aide -y
Initial-Scan:
# Datenbank initialisieren
sudo aideinit
⚠️ Initial-Scan kann dauern. Auf einem großen Server kann der erste Scan 10-30 Minuten dauern. Führe ihn nicht während Produktivbetrieb aus.
Datenbank aktivieren:
# Neue Datenbank verschieben
sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
Cron-basierte Überprüfung:
sudo nano /etc/cron.d/aide-check
# Täglicher Check um 03:00
0 3 * * * root /usr/bin/aide --check | mail -s "AIDE Report" admin@example.com
Verifikation:
# Manuellen Check durchführen
sudo aide --check
# Erwartete Ausgabe bei sauberem System:
# AIDE found NO differences between database and filesystem.
Automatische Sicherheits-Patches (Unattended-Upgrades)
Eine rein manuelle Patch-Routine lässt Zero-Day-Lücken und bekannte CVEs tagelang ungeschützt. Automatische Sicherheits-Patches schließen kritische Schwachstellen zeitnah, während needrestart veraltete Prozesse nach Bibliotheks-Updates neu startet.
Installation & Aktivierung:
# Automatische Updates und Neustart-Prüfer installieren
sudo apt install unattended-upgrades needrestart -y
# Konfiguration aktivieren
sudo dpkg-reconfigure -plow unattended-upgrades
Konfiguration anpassen (/etc/apt/apt.conf.d/50unattended-upgrades):
// Nur Sicherheitsupdates automatisch installieren
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
// Automatisch verwaiste Abhängigkeiten entfernen
Unattended-Upgrade::Remove-Unused-Dependencies "true";
// Automatischer Neustart bei Kernel-Updates um 04:00 Uhr
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Verifikation:
# Testlauf ohne Änderungen durchführen (Dry-Run)
sudo unattended-upgrade --dry-run --debug
Mandatory Access Control (AppArmor)
AppArmor schränkt Anwendungen (wie Nginx, Unbound oder PHP-FPM) auf die absolut notwendigen Dateisystem- und Netzwerk-Zugriffe ein. Selbst wenn ein Dienst eine Remote-Code-Execution-Schwachstelle hat, verhindert das AppArmor-Profil das Ausbrechen ins System.
Status und aktive Profile prüfen:
sudo aa-status
Erwartete Ausgabe:
apparmor module is loaded.
XX profiles are in enforce mode.
0 processes are unconfined but have a profile defined.
Profile in den Enforce-Modus schalten:
# Profile aktivieren und strikt erzwingen
sudo apt install apparmor-utils -y
sudo aa-enforce /etc/apparmor.d/*
💡 Tipp: Neue oder eigene Profile kannst du mit
aa-genprof <programm>im interaktiven Lernmodus aufzeichnen und anschließend peraa-enforcescharf schalten.
Netzwerk-Härtung
nftables: Die moderne Firewall
nftables hat iptables als Standard-Firewall-Manager abgelöst. Unter Ubuntu 24.04 und 26.04 ist nftables der Kernel-Standard. Wer noch iptables nutzt, arbeitet mit einer Kompatibilitätsschicht – das funktioniert, aber nftables ist effizienter und bietet klarere Syntax.
Grundstruktur:
# Aktuelle Tabellen anzeigen
sudo nft list ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
# Regeln hier
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}
Firewall-Grundregeln erstellen:
sudo nano /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
# Erlaube etablierte Verbindungen
ct state established,related accept
# Erlaube Loopback
iif lo accept
# Erlaube ICMP (Ping)
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# Erlaube SSH (Port 22)
tcp dport 22 accept
# Erlaube HTTP/HTTPS (falls Webserver)
tcp dport { 80, 443 } accept
# Logging für abgelehnten Traffic
log prefix "nft-drop: " counter drop
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}
Service starten:
sudo systemctl enable nftables
sudo systemctl start nftables
⚠️ Reihenfolge beachten: Bei Remote-Servern immer zuerst SSH (Port 22) erlauben, bevor du die Standard-Policy auf
dropsetzt. Sonst sperrst du dich selbst aus.
Rate-Limiting für SSH:
# SSH-Rate-Limit: Maximal 5 Verbindungen pro Minute
sudo nft add rule inet filter input tcp dport 22 meter ssh-rate { limit rate 5/minute burst 5 packets } accept
Verifikation:
# Aktive Regeln prüfen
sudo nft list ruleset
# Verbindungen prüfen
sudo ss -tulnp | grep -E "22|80|443"
# Drops in Logs prüfen
sudo journalctl -k | grep "nft-drop"
CrowdSec + nftables Integration
CrowdSec arbeitet mit nftables über den Firewall-Bouncer zusammen. Der Agent analysiert Logs und der Bouncer setzt Banns über nftables-Regeln durch.
Bouncer-Regeln prüfen:
# CrowdSec-spezifische Chains anzeigen
sudo nft list chain inet crowdsec-crowdsec-block
table inet crowdsec-crowdsec {
chain crowdsec-block {
type filter hook input priority filter; policy accept;
ip saddr @crowdsec-blacklist-jump drop
ip6 saddr @crowdsec-blacklist6-jump drop
}
}
Traffic-Flow:
┌─────────────────────────────────────────────────────────────┐
│ Firewall Traffic-Flow │
├─────────────────────────────────────────────────────────────┤
│ │
│ [Eingehender Traffic] │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ CrowdSec Check │ ◀── Whitelist? ──▶ ACCEPT │
│ │ (blacklist) │ ◀── Ban? ────────▶ DROP + LOG │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Firewall Rules │ ◀── SSH/HTTP ────▶ ACCEPT │
│ │ (nftables) │ ◀── Rest ────────▶ DROP │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ CrowdSec Agent │ ◀── Logs analysieren │
│ │ (Log-Analyse) │ ◀── Decisions erstellen │
│ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
TLS-Konfiguration
Für Webserver ist TLS-1.3 der aktuelle Standard. TLS 1.2 ist noch akzeptabel, aber ältere Versionen (1.0, 1.1) sollten deaktiviert werden.
Let's Encrypt mit Certbot:
# Certbot installieren
sudo apt install certbot -y
# Zertifikat anfordern (Nginx)
sudo certbot --nginx -d example.com
# Zertifikat anfordern (Apache)
sudo certbot --apache -d example.com
TLS-Konfiguration für Nginx:
sudo nano /etc/nginx/snippets/ssl-params.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
# Security Headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
Zertifikat-Auto-Renewal:
# Cron-Job für automatische Erneuerung
sudo crontab -e
# Täglich um 02:30, Zertifikat bei Bedarf erneuern
30 2 * * * certbot renew --quiet --post-hook "systemctl reload nginx"
Verifikation:
# TLS-Konfiguration testen
openssl s_client -connect example.com:443 -tls1_3
# Zertifikats-Details prüfen
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
DNS-over-HTTPS
Standardmäßig sendet dein Server DNS-Anfragen unverschlüsselt über UDP Port 53. Jeder auf dem Weg kann sehen, welche Domains du auflöst. DNS-over-HTTPS (DoH) verschlüsselt diese Anfragen.
Unbound als lokaler Resolver:
sudo apt install unbound -y
Konfiguration:
sudo nano /etc/unbound/unbound.conf.d/hardening.conf
server:
interface: 127.0.0.1
access-control: 127.0.0.0/8 allow
# DNSSEC aktivieren
auto-trust-anchor-file: /var/lib/unbound/root.key
# Datenschutz
hide-identity: yes
hide-version: yes
# Performance
msg-cache-slabs: 8
rrset-cache-slabs: 8
infra-cache-slabs: 8
key-cache-slabs: 8
Mit Cloudflare DoH aufwärtsauflösen:
forward-zone:
name: "."
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 9.9.9.9@853#dns.quad9.net
forward-tls-upstream: yes
Service starten:
sudo systemctl enable unbound
sudo systemctl start unbound
Verifikation:
# DNS-Auflösung testen
dig example.com @127.0.0.1
# DNSSEC-Test
dig dnssec-failed.org @127.0.0.1
# Erwartung: SERVFAIL (DNSSEC-Breaker korrekt erkannt)
# DNS-over-HTTPS testen
curl -v https://cloudflare-dns.com/dns-query?name=example.com 2>&1 | grep "HTTP/2"
💡 Empfehlung: Für Server mit vielen Clients (z. B. Homelab) ist Unbound als lokaler Resolver die beste Wahl. Clients konfigurierst du mit
127.0.0.1als DNS-Server, Unbound resolved aufwärts über DoH/DoT.
Automatisierung
Ansible-Playbook für Server-Härtung
Die bisherigen Abschnitte haben gezeigt, welche Konfigurationen nötig sind. Einzelne Befehle auf jedem Server auszuführen ist arbeitsintensiv und fehleranfällig. Ansible automatisiert das – du beschreibst den gewünschten Zustand, und Ansible setzt ihn auf allen Servern um.
Projektstruktur:
mkdir -p ~/ansible-hardening/{roles,inventory,group_vars}
Inventory:
nano ~/ansible-hardening/inventory/hosts.ini
[production]
server1 ansible_host=192.168.1.100
server2 ansible_host=192.168.1.101
[homelab]
server3 ansible_host=192.168.10.50
[all:vars]
ansible_user=admin
ansible_python_interpreter=/usr/bin/python3
Playbook:
nano ~/ansible-hardening/hardening.yml
---
- name: Linux Server Härtung
hosts: all
become: yes
vars:
ssh_port: 22
allowed_users:
- admin
enable_crowdsec: true
enable_aide: true
tasks:
# System-Updates
- name: System aktualisieren
apt:
update_cache: yes
upgrade: dist
# SSH-Härtung
- name: SSH-Konfiguration härten
lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
loop:
- { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin no' }
- { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' }
- { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 3' }
- { regexp: '^#?LoginGraceTime', line: 'LoginGraceTime 30' }
notify: sshd neu starten
# Sysctl-Härtung
- name: Sysctl-Parameter setzen
sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
state: present
reload: yes
loop:
- { name: 'net.ipv4.conf.all.rp_filter', value: '1' }
- { name: 'net.ipv4.conf.all.accept_redirects', value: '0' }
- { name: 'kernel.randomize_va_space', value: '2' }
- { name: 'kernel.dmesg_restrict', value: '1' }
# Unnötige Services
- name: Unnötige Services deaktivieren
systemd:
name: "{{ item }}"
state: stopped
enabled: no
masked: yes
loop:
- cups
- bluetooth
- avahi-daemon
# Audit-Logging
- name: Auditd installieren
apt:
name: auditd
state: present
- name: Audit-Regeln deployen
copy:
src: files/audit-rules.rules
dest: /etc/audit/rules.d/hardening.rules
notify: auditd neu starten
handlers:
- name: sshd neu starten
systemd:
name: sshd
state: restarted
- name: auditd neu starten
systemd:
name: auditd
state: restarted
Playbook ausführen:
cd ~/ansible-hardening
ansible-playbook -i inventory/hosts.ini hardening.yml
💡 Idempotenz: Ansible ist idempotent – du kannst das Playbook mehrmals ausführen, ohne dass sich am System unnötig etwas ändert. Nur abweichende Zustände werden korrigiert.
Verifikation:
# Prüfe ob alle Server erreichbar sind
ansible all -i inventory/hosts.ini -m ping
# SSH-Konfiguration prüfen
ansible all -i inventory/hosts.ini -m shell -a "sshd -T | grep permitrootlogin"
Erwartete Ausgabe:
server1 | CHANGED | rc=0 >>
permitrootlogin no
server2 | CHANGED | rc=0 >>
permitrootlogin no
CIS-Scoring mit OpenSCAP
CIS (Center for Internet Security) Benchmarks definieren Sicherheitsstandards für verschiedene Systeme. OpenSCAP prüft dein System gegen diese Benchmarks und gibt einen Score aus.
Installation:
sudo apt install libopenscap8 scap-security-guide -y
Ubuntu 24.04/26.04 Benchmark ausführen:
# Verfügbare Profiles anzeigen
oscap info /usr/share/xml/scap/ssg/content/ssg-ubuntu2404-ds.xml | grep "Profile"
# CIS-Level-1-Scan
sudo oscap xccdf eval \
--profile cis_level1_server \
--results cis-results.xml \
--report cis-report.html \
/usr/share/xml/scap/ssg/content/ssg-ubuntu2404-ds.xml
Report im Browser öffnen:
# Report auf den lokalen Rechner kopieren und öffnen
scp admin@server:/tmp/cis-report.html ~/Desktop/
Score interpretieren:
| Score | Bedeutung |
|---|---|
| 100% | Alle CIS-Empfehlungen erfüllt |
| 80-99% | Guter Stand, kleinere Anpassungen nötig |
| 60-79% | Grundkonfiguration, einige Härtungen fehlen |
| < 60% | Kritische Lücken, dringender Handlungsbedarf |
Automatisierte Remediation (Vorsicht):
# Nur wenn du die Auswirkungen verstehst
sudo oscap xccdf generate fix \
--profile cis_level1_server \
--fix-type ansible \
--output cis-remediation.yml \
/usr/share/xml/scap/ssg/content/ssg-ubuntu2404-ds.xml
⚠️ Remediation nur gezielt: Automatische Remediation kann funktionierende Konfigurationen überschreiben. Prüfe jede Änderung vor dem Ausführen. Im Zweifel: Manuell und mit Verstand.
Verifikation:
# Score prüfen
oscap xccdf generate report cis-results.xml > /dev/null
grep -E "score|pass|fail" cis-results.xml | tail -10
Härtungs-Checkliste
| Schritt | Maßnahme | Wirkung |
|---|---|---|
| 1 | SSH: PasswordAuthentication no, PermitRootLogin no |
Brute-Force blockiert |
| 2 | FIDO2/YubiKey einrichten | Passwortlose, hardwarebasierte Auth |
| 3 | CrowdSec Agent + Bouncer installieren | Kollaborativer Intrusion-Schutz |
| 4 | Sysctl-Härtung anwenden | Kernel-Parameter absichern |
| 5 | Unnötige Services maskieren | Angriffsfläche verkleinern |
| 6 | auditd aktivieren | Sicherheitsereignisse protokollieren |
| 7 | AIDE initialisieren | File-Integrity-Überwachung |
| 8 | nftables Firewall aufsetzen | Netzwerk-Traffic filtern |
| 9 | TLS 1.3 + Security Headers | Webserver absichern |
| 10 | Unbound + DoH konfigurieren | DNS-Anfragen verschlüsseln |
Jeder Schritt baut auf dem vorherigen auf. Die Reihenfolge ist bewusst so gewählt: Zuerst die kritischsten Schwachstellen (SSH, CrowdSec), dann System-, dann Netzwerk-Schicht.
Fazit
Serverhärtung ist kein einmaliges Projekt, sondern ein fortlaufender Prozess. Die in diesem Artikel beschriebenen Maßnahmen – SSH mit FIDO2, CrowdSec, Kernel-Härtung, nftables und TLS – bilden das Fundament, auf dem jedes produktive Linux-System aufbauen sollte.
Wichtig ist der Startpunkt: Nicht alles auf einmal, sondern schrittweise. Die Härtungs-Checkliste oben gibt die Reihenfolge vor. Mit jedem Schritt wird die Angriffsfläche kleiner, und die Kombination aus mehreren Maßnahmen schafft ein robustes Sicherheitsprofil, das einzelnen Schwachstellen nicht das gleiche Schadenspotenzial lässt.
Für Teams lohnt sich die Automatisierung mit Ansible – was einmal konfiguriert ist, kann auf beliebig viele Server übertragen werden. CIS-Benchmarks mit OpenSCAP geben den Score vor und zeigen, wo noch Luft nach oben ist.
Ein gehärteter Server ist kein Server, der gegen alles abgesichert ist. Es ist ein Server, bei dem du weißt, was abgesichert ist und was nicht – und warum.
Weiterführende Ressourcen
Offizielle Dokumentationen
OpenSSH FIDO2 CrowdSec Dokumentation nftables Wiki CIS Benchmarks OpenSCAP Dokumentation
Tools & Pakete
YubiKey Manager CrowdSec Hub Certbot Unbound
Verwandte Artikel auf admindocs.de
| Thema | Artikel |
|---|---|
| VPN-Grundlagen | WireGuard VPN-Server einrichten |
| Container | Ubuntu 26.04 Docker |
| Automatisierung | Ansible Grundlagen |
| Netzwerk | Netzwerkprobleme analysieren |
| Grundlagen | LPIC-1 Serie |
| Server | Ubuntu-Upgrade: 22.04 auf 24.04 LTS |