Mein wöchentlicher fail2ban-Report sah lange Zeit immer gleich aus: rund 200 Treffer pro Woche, wie Uhrwerk. Dieselben IPs aus denselben Ecken des Internets, dieselben Passwort-Versuche gegen Port 22, dieselben Skripte, die sich durch die Adressräume wühlen. Dann habe ich an einem Sonntagnachmittag die SSH-Anmeldung auf Key-only umgestellt, TOTP für Root eingerichtet und die sshd_config härter gemacht. Eine Woche später kam die Mail. 0 Treffer.

Nicht 50, nicht 20. Null. fail2ban hatte nichts mehr zu tun, weil gar nichts mehr ankam. Die Scanner schicken ihre Versuche trotzdem noch, aber sshd antwortet nicht mehr darauf. Dieser Artikel ist die Checkliste, die ich damals abgearbeitet habe, plus die zwei Dinge, die ich dabei gelernt habe: TOTP für Root und die Fingerprint-Prüfung mit dem Hash-Generator.

sshd_config Härtungs-Checkliste: 8 Einstellungen im Überblick

Warum Passwort-Login das Problem ist

Ein Passwort ist etwas, das du kennst. Ein SSH-Key ist etwas, das du hast. Klingt banal, macht aber den Unterschied zwischen einem Server, der im Log nur Protokoll rauscht, und einem, der jede Nacht mit Brute-Force gefüttert wird.

Die Scanner der Welt probieren nicht klug. Sie nehmen Wörterbücher, nehmen Standardpasswörter, nehmen die hundert häufigsten Kombinationen. Gegen ein gutes Passwort beißen sie sich die Zähne aus. Gegen ein schlechtes nicht. Und genau da liegt das Problem: Du kannst nicht garantieren, dass alle deine Benutzer gute Passwörter haben. Ich konnte es bei mir nicht einmal selbst. Nach einem Jahr Admin-Arbeit wusste ich, dass irgendwo ein Passwort lag, das ich längst hätte ändern sollen.

Mit Key-only ist diese Angriffsfläche komplett weg. Es gibt schlicht keinen Passwort-Pfad mehr, den ein Scanner ausprobieren könnte. Das ist der Grund, warum fail2ban nach dem Umbau bei Null stand: nicht weil die Angreifer aufhören, sondern weil sshd ihnen nichts mehr zu raten gibt.

So lief der Umbau ab

Den großen Schalter habe ich nicht auf einmal umgelegt. Das ist der Fehler, den andere machen, und genau so schließt man sich aus. Mein Plan hatte drei Stufen.

Stufe eins: Für jedes Konto einen Key erzeugen, auf den Server kopieren und einmal per Key einloggen, um zu beweisen, dass es funktioniert. Stufe zwei: Die Zeilen aus der Checkliste unten in die sshd_config schreiben und den Dienst neu laden. Stufe drei: Die alte Session offen lassen, eine zweite öffnen und erst dann die erste schließen. Wenn etwas schiefgeht, hast du noch einen Rettungsanker.

Gedauert hat das Ganze etwa eine Stunde. Das Lesen des Reports eine Woche später hat zehn Sekunden gedauert: 0 Treffer.

Keys erzeugen: ed25519 statt RSA

Bevor die Checkliste greift, brauchst du gute Keys. Ich erzeuge sie mit ssh-keygen -t ed25519. Ed25519-Keys sind kurz, schnell und gelten heute als der sichere Standard. RSA mit 4096 Bit funktioniert auch, aber die Dateien sind klobig, und der Overhead ist unnötig.

ssh-keygen -t ed25519 -a 100
ssh-copy-id admin@server.example.com

Das -a 100 erhöht die Iterationen der Passphrase-Ableitung. Kostet beim Login eine halbe Sekunde, macht aber das Knacken der Passphrase um Größenordnungen teurer. Die Passphrase selbst kommt aus dem Passwort-Generator.

Ein Key pro Gerät, nicht einer für alles. Laptop, Desktop, Server-Konsole: drei Keys, drei Dateien. Wenn ein Gerät weg ist, lösche ich nur seinen Key aus der authorized_keys, und der Rest funktioniert weiter.

Die sshd_config-Checkliste

Die Tabelle oben ist die Reihenfolge, in der ich die Datei abgearbeitet habe. Jede Zeile hat einen Grund, und jede kostet dich als Admin ein bisschen Bequemlichkeit. Das ist der Deal.

# /etc/ssh/sshd_config: die wichtigen Zeilen
PasswordAuthentication no
PermitRootLogin prohibit-password
MaxAuthTries 3
PubkeyAuthentication yes
AuthenticationMethods publickey
AllowUsers admin
LoginGraceTime 30

Die Zeile, die die meisten Treffer killt, ist PasswordAuthentication no. Danach nimmt sshd gar keine Passwörter mehr an, egal wie oft sie angeboten werden. PermitRootLogin prohibit-password erlaubt Root weiterhin per Key, aber niemals per Passwort. Das ist der Kompromiss für einen Server ohne Konsole: Ich komme rein, aber nur mit dem richtigen Schlüssel.

MaxAuthTries 3 begrenzt die Versuche pro Verbindung, LoginGraceTime 30 die Lebensdauer einer halb offenen Verbindung. Beides drosselt genau die Verhaltensweisen, die fail2ban vorher täglich gezählt hat. AuthenticationMethods publickey erzwingt, dass wirklich nur die Key-Methode zählt. Und AllowUsers admin verkleinert die Menge der Konten, die überhaupt angesprochen werden dürfen.

Nach jeder Änderung: sshd -t zum Testen, dann systemctl restart ssh. Und halte dich an der Konsole fest, bevor du die Session schließt. Ich habe die Verbindung einmal verloren, weil ein Tippfehler in der Datei lag und die Firewall die neue Session blockierte. Seitdem gilt bei mir: erst eine zweite Session öffnen, dann neu starten.

TOTP für Root: Der zweite Faktor

Ein Key allein ist stark, aber ein gestohlener Key ist wie ein gestohlener Wohnungsschlüssel. Deshalb gehört zu Root ein zweiter Faktor. Bei mir ist das TOTP: eine sechsstellige Zahl, die alle 30 Sekunden neu entsteht, erzeugt von einer App auf dem Handy.

apt install libpam-google-authenticator
google-authenticator        # QR-Code scannen, Secret sichern
# /etc/pam.d/sshd ergänzen:
auth required pam_google_authenticator.so

Dann in der sshd_config: AuthenticationMethods publickey,keyboard-interactive. Erst der Key, dann der Code. Wer beides nicht vorweisen kann, bleibt draußen.

Den QR-Code scanne ich mit der Authenticator-App, aber das Secret hebe ich zusätzlich als Backup auf. Wer sein Handy verliert und kein Backup hat, steht vor verschlossener Tür. Mir ist das einmal passiert, auf einem Server, der 600 Kilometer entfernt stand. Die Zutrittskarte fürs Rechenzentrum hatte ich an dem Tag auch nicht dabei. Seitdem liegen die Recovery-Codes ausgedruckt im Tresor, zusammen mit den Backups der SSH-Keys.

Die Passphrasen für die Keys selbst erzeuge ich mit dem Passwort-Generator. Vier zufällige Wörter, die das Tool würfelt, sind besser als jede selbst ausgedachte Kombination. Eigene Wortketten sind vorhersagbarer, als man denkt.

Host-Key-Fingerprints prüfen

Die erste Verbindung zu einem neuen Server ist der Moment, in dem Man-in-the-Middle-Angriffe passieren. Dein Client zeigt dir den Fingerprint des Host-Keys. Wenn du ihn nicht prüfst, akzeptierst du jeden, der sich als dein Server ausgibt.

ssh-keyscan -t ed25519 server.example.com \
  | ssh-keygen -lf -

Das Ergebnis ist ein Hash wie SHA256:9tK…=. Diesen Wert vergleiche ich mit dem, was mir der Serverbesitzer über einen zweiten Kanal geschickt hat. Und wenn ich einen Hash selbst nachrechnen will, nehme ich den Hash-Generator und lasse mir SHA-256 über die .pub-Datei ausgeben. Der Fingerprint, den ssh-keygen zeigt, ist im Kern genau das: ein SHA-256-Hash über den Public Key.

Kleiner Hinweis am Rande: Der Fingerprint in /etc/ssh/ssh_host_ed25519_key.pub ist der Host-Fingerprint. Dein eigener Key-Fingerprint steht in ~/.ssh/id_ed25519.pub. Beide Dateien kann der Hash-Generator nachrechnen, und beide sollten zu dem passen, was du erwartest.

Einmal eingecheckt, landet der Fingerprint in der known_hosts. Ab dann merkt sich dein Client, welcher Key zu welchem Server gehört. Weicht er später ab, schlägt die Verbindung fehl, und das ist gut so. Genau das hat mich einmal vor einem umgebauten Server bewahrt, der nach einem Migrationstag einen frischen Host-Key hatte. Ohne die Prüfung hätte ich den neuen Key blind akzeptiert.

Was fail2ban danach noch meldet

Null Treffer heißt nicht, dass nichts mehr passiert. Die Scanner kommen weiterhin, sie machen nur keinen Lärm mehr. Was bleibt, sind die stillen Sachen: Port-Scans, die deine offenen Ports inventarisieren, gelegentliche Versuche auf andere Dienste, Log-Einträge, die du getrost ignorieren darfst.

Mein Report sieht heute so aus: Statt 200 Passwort-Versuchen pro Woche stehen da zwei oder drei Einträge im Monat, und die sind fast immer harmlos. Wenn doch mal eine IP auffällig oft gegen Port 22 klopft, banne ich sie kurz von Hand. fail2ban schläft nicht, es hat nur weniger zu tun.

# /etc/fail2ban/jail.local
[sshd]
enabled = true
maxretry = 3
bantime = 1h

Was ich aus dem Umbau mitgenommen habe: Die besten Sicherheitsmaßnahmen sind die, die dafür sorgen, dass gar nichts erst ankommt. fail2ban ist ein gutes Netz, aber es ist besser, die Fische gar nicht erst in den Teich zu lassen.

Die Härtungs-Einzeiler: PasswordAuthentication no · PermitRootLogin prohibit-password · AuthenticationMethods publickey · MaxAuthTries 3 · TOTP für Root · danach sshd -t und neu starten.