
Auf einem Server, der aus dem Internet erreichbar ist, sieht das Anmelde-Protokoll nach einer Nacht so aus:
Failed password for root from 203.0.113.17 port 41022 ssh2
Failed password for invalid user admin from 198.51.100.8 port 55710 ssh2
Failed password for invalid user ubuntu from 198.51.100.8 port 55934 ssh2Das sind keine Angreifer, die dich persönlich meinen. Das sind Skripte, die das ganze Internet abklappern und auf jedem Port 22 dieselben zweihundert Passwörter durchprobieren. Steht dein Server nur im Heimnetz hinter dem Router, kommen sie nicht bis zu ihm. Trotzdem würde ich SSH auch dort absichern, aus zwei Gründen: Irgendwann öffnest du doch eine Portfreigabe oder ein VPN, und dann soll die Tür schon zu sein. Und der Schlüssel-Login ist nebenbei bequemer als jedes Passwort.
Das Rezept ist seit Jahren dasselbe und passt auf eine Hand: Schlüssel statt Passwort, root aussperren, einen Wächter davorsetzen. Was sich geändert hat, ist die Stelle, an der man es einträgt. Debian und Ubuntu lesen seit einigen Versionen einen Ordner mit Zusatzdateien, und Ubuntu legt dort seit 24.04 selbst eine ab, die deine Einstellung überstimmt, wenn du sie an der klassischen Stelle machst. Genau darüber stolpern gerade viele. Dazu später mehr.
Was du brauchst
- Einen Server mit Debian 12 oder 13 oder Ubuntu 24.04 oder 26.04, auf dem du dich schon per SSH mit Passwort anmelden kannst.
- Einen Benutzer, der nicht root ist und
sudodarf. Falls du bisher nur als root arbeitest: erst einen anlegen, sonst sperrt dich Schritt 3 aus. - Einen Laptop oder PC mit SSH-Client. Linux und macOS haben ihn, Windows 10 und 11 seit ein paar Jahren auch (im PowerShell-Fenster).
- Einen Weg zum Server, der nicht über SSH geht: die Konsole in Proxmox, ein Monitor am Rechner oder die Webkonsole deines Hosters. Du wirst ihn vermutlich nicht brauchen. Aber wenn, dann dringend.
Ob die Voraussetzungen passen, prüfst du auf dem Server mit drei Befehlen. Die erste Zeile zeigt die Version, die zweite muss active (running) enthalten, die dritte darf nach dem Passwort keine Fehlermeldung bringen.
grep PRETTY_NAME /etc/os-release
systemctl status ssh | head -3
sudo -vDer zweite Befehl heißt auf beiden Systemen ssh, nicht sshd. Das ist auf Debian und Ubuntu seit jeher so und verwirrt jeden, der von CentOS kommt.
- Server im Heimnetz:
192.168.178.50 - Dein Benutzer auf dem Server:
sakis - Dein Heimnetz:
192.168.178.0/24 - Name für den Schlüssel:
laptop-sakis - Die neue Einstellungsdatei:
/etc/ssh/sshd_config.d/10-homelab.conf
Alles Weitere läuft in zwei Fenstern: eines auf dem Laptop für die Schlüssel, eines auf dem Server für die Einstellungen. Ich schreibe jeweils dazu, wo du gerade bist.
Schritt 1: Schlüsselpaar auf dem Laptop erzeugen
Auf dem Laptop, nicht auf dem Server. Der Befehl fragt nach einem Speicherort (Enter für den Vorschlag) und zweimal nach einer Passphrase. Die Passphrase ist das Passwort für die Schlüsseldatei selbst: Wer dir den Laptop klaut, hat damit noch keinen Server. Ich würde eine setzen. Die Manpage sagt dazu trocken, dass es keinen Weg gibt, eine vergessene Passphrase wiederherzustellen. Also ab damit in den Passwort-Manager, zum Beispiel deinen eigenen Vaultwarden.
ssh-keygen -t ed25519 -C "laptop-sakis"
ls ~/.ssh/id_ed25519*-t ed25519 wählt das Verfahren, das OpenSSH seit Version 9.5 ohnehin als Standard nimmt. -C hängt einen Kommentar an, damit du in einem Jahr noch weißt, von welchem Gerät der Schlüssel stammt. Der zweite Befehl ist die Kontrolle: Er muss zwei Dateien zeigen, id_ed25519 und id_ed25519.pub. Die ohne Endung ist die private. Die kopierst du nirgendwohin, auch nicht „nur kurz" auf den Server.
Schritt 2: Öffentlichen Schlüssel auf dem Server hinterlegen
Immer noch auf dem Laptop. ssh-copy-id meldet sich ein letztes Mal mit Passwort an und hängt den öffentlichen Schlüssel auf dem Server an die Datei ~/.ssh/authorized_keys. Ordner und Datei legt es an, wenn sie fehlen.
ssh-copy-id sakis@192.168.178.50
ssh sakis@192.168.178.50Der zweite Befehl ist der Test: Fragt der Server nur noch nach der Passphrase deines Schlüssels (oder nach gar nichts, wenn du keine gesetzt hast) und nicht mehr nach dem Passwort des Benutzers, ist der Schlüssel drin. Auf dem Server siehst du es auch im Protokoll:
sudo journalctl -u ssh -n 5 --no-pagerAccepted publickey for sakis from 192.168.178.20 port 53522 ssh2: ED25519 SHA256:…Windows ohne ssh-copy-id: Der Windows-Client bringt das Hilfsprogramm nicht mit. Dann machst du von Hand, was es tun würde: Auf dem Laptop den Inhalt von id_ed25519.pub anzeigen und kopieren (eine Zeile, beginnt mit ssh-ed25519), dann auf dem Server die Datei anlegen und die Zeile einfügen. Speichern in nano: Strg+O, Enter, schließen mit Strg+X.
mkdir -p ~/.ssh && chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keysDie beiden chmod sind kein Beiwerk. Die sshd-Manpage sagt, dass der Server einen Schlüssel ignoriert, wenn Ordner oder Datei für andere Benutzer schreibbar sind. Das ist die zweithäufigste Ursache für „Schlüssel drin, fragt trotzdem nach Passwort".
Schritt 3: Passwort-Login und root abschalten
Jetzt auf dem Server. Und jetzt der Backup-Tick, du kennst ihn: Lass das Fenster, in dem du gerade angemeldet bist, bis zum Ende dieses Schritts offen. Jeder Test läuft in einem zweiten Fenster. Solange die alte Sitzung lebt, kannst du jede Änderung zurücknehmen.
Die Einstellungen kommen in eine eigene Datei im Ordner /etc/ssh/sshd_config.d/. Debian liest den Ordner seit Version 11, Ubuntu ebenso, und beide haben dafür ganz oben in der Hauptdatei eine Include-Zeile. Warum eine eigene Datei statt der Hauptdatei? Weil das Paket bei Updates die Hauptdatei anfassen darf, deine Datei aber nicht. Und weil der Name zählt.
sudo nano /etc/ssh/sshd_config.d/10-homelab.confDiese vier Zeilen hinein, speichern (Strg+O, Enter), schließen (Strg+X):
PasswordAuthentication no
PermitRootLogin no
KbdInteractiveAuthentication no
MaxAuthTries 3Zeile eins schaltet den Passwort-Login ab, ab jetzt öffnet nur der Schlüssel. Zeile zwei sperrt root komplett aus; der Standard ist prohibit-password, root dürfte also noch mit Schlüssel, und das brauchst du nicht, du hast sudo. Zeile drei schließt den zweiten Passwort-Weg, den PAM anbietet; Debian und Ubuntu haben ihn zwar schon aus, aber die Hauptdatei kann sich ändern, deine Datei nicht. Zeile vier senkt die Rateversuche pro Verbindung von sechs auf drei. Das erspart dem Bot nichts, aber dem Protokoll die Hälfte der Zeilen.
Bevor du den Dienst neu startest, prüfst du zweimal. Einmal die Syntax: sshd -t sagt bei einer korrekten Datei gar nichts, das ist hier die gute Nachricht. Und einmal die Wirkung: sshd -T zeigt, was der Server nach dem Zusammenlesen aller Dateien wirklich anwendet.
sudo sshd -t
sudo sshd -T | grep -Ei "^(passwordauthentication|permitrootlogin|maxauthtries)"passwordauthentication no
permitrootlogin no
maxauthtries 3Steht dort passwordauthentication yes, hat eine andere Datei deine überstimmt. Auf Ubuntu ist das fast immer 50-cloud-init.conf, siehe Stolpersteine. Erst wenn die Ausgabe stimmt, startest du neu. Deine offene Sitzung übersteht das, weil jede Verbindung ihren eigenen Prozess hat.
sudo systemctl restart sshDer Test kommt in einem zweiten Fenster auf dem Laptop, und zwar in beide Richtungen. Erst normal anmelden: muss klappen. Dann mit einer Option, die dem Client den Schlüssel verbietet: muss scheitern, und zwar mit genau dieser Meldung.
ssh sakis@192.168.178.50 "echo schluessel ok"
ssh -o PubkeyAuthentication=no sakis@192.168.178.50sakis@192.168.178.50: Permission denied (publickey).Die zweite Zeile ist die schönste Fehlermeldung des Tages: Der Server nennt nur noch publickey als Möglichkeit, das Wort password fehlt in der Klammer. Jetzt darfst du das alte Fenster schließen.
Schritt 4: fail2ban davorsetzen
Die eigenen Werte kommen nie in jail.conf, die überschreibt das nächste Paket-Update. Ganz oben in dieser Datei steht das sogar in Großbuchstaben. Stattdessen legst du jail.local daneben an.
sudo apt install fail2ban
sudo nano /etc/fail2ban/jail.local[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 192.168.178.0/24
[sshd]
enabled = true
backend = systemdSpeichern, schließen, Dienst neu starten. Dann fragst du fail2ban selbst, ob es das SSH-Gefängnis bewacht.
sudo systemctl restart fail2ban
sudo fail2ban-client status sshdStatus for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:Die Zeile backend = systemd sagt fail2ban, dass es das Journal lesen soll statt einer Logdatei. Auf Debian 13 bringt das Paket die Zeile schon mit, auf einem frisch installierten Debian 12 nicht, und dort gibt es die Datei /var/log/auth.log gar nicht mehr. Die Zeile schadet nirgends und erspart dir den häufigsten Fehler aus den Stolpersteinen.
Die Einstellungen, die den Unterschied machen
Der Server ist jetzt dicht. Vier Werte in den zwei Dateien würde ich trotzdem noch einmal bewusst anfassen, weil die Voreinstellung jeweils für einen anderen Server gedacht ist als deinen.
ignoreipmit deinem Heimnetz. Ohne diese Zeile sperrt fail2ban auch dich, wenn du dich beim Passphrase-Tippen fünfmal vertust oder ein Skript von deinem Laptop mit falschem Benutzernamen anklopft. Genau dafür ist die Liste lautjail.confgedacht. Mit dem Heimnetz drin zählt der Wächter nur Fremde.bantimeauf eine Stunde statt zehn Minuten. Zehn Minuten sind der Wert ausjail.conf. Bots warten die ab und machen weiter. Wer es gründlicher will, setzt zusätzlichbantime.increment = true: Dann wächst die Sperre bei jedem Wiederkommen. Das steht so in der Doku zujail.confund ist die Einstellung, die das Protokoll am spürbarsten leert.AllowUsers sakisin deiner Datei. Standardmäßig darf jedes Konto auf dem Server per SSH rein, auch das von Diensten, die du später installierst und die sich selbst ein Konto anlegen. MitAllowUsers(mehrere Namen durch Leerzeichen) ist die Liste explizit. Vorher in Ruhe überlegen, wer wirklich draufsteht: Wer fehlt, kommt nicht mehr rein, auch mit Schlüssel.- Ein zweiter Schlüssel für den zweiten Rechner. Ein Schlüssel pro Gerät, nie die private Datei kopieren. Geht ein Laptop verloren, löschst du auf dem Server nur seine Zeile aus
authorized_keys, der Rest arbeitet weiter. Das ist der Grund für den Kommentar aus Schritt 1: Ohne ihn siehst du in der Datei zwei Zeilen Zeichensalat und weißt nicht, welche weg soll.
Nach jeder Änderung an der sshd-Datei dasselbe Ritual wie in Schritt 3: sudo sshd -t, dann sudo systemctl restart ssh, dann der Test aus dem zweiten Fenster. Nach Änderungen an jail.local reicht sudo systemctl restart fail2ban.
Was hier bewusst fehlt: der Port-Wechsel. Er hält nur die dümmsten Bots fern, weil die anderen alle Ports abklopfen, und auf Ubuntu lauscht seit 22.10 systemd auf dem Port statt sshd selbst, sodass die Zeile Port je nach Version einen Umweg über eine Socket-Datei braucht. Das Ubuntu-Forum hat dazu einen eigenen Erklär-Thread. Ich würde die halbe Stunde lieber in fail2ban stecken.
Stolpersteine aus fremder Praxis
Ubuntu: „Ich habe PasswordAuthentication no gesetzt, und es passiert nichts." Der häufigste Treffer, seit Ubuntu 24.04 draußen ist. Der Installer legt über cloud-init die Datei /etc/ssh/sshd_config.d/50-cloud-init.conf mit PasswordAuthentication yes an. Wer die Einstellung in der Hauptdatei macht, verliert, weil der Ordner vor der Hauptdatei gelesen wird. Auf Launchpad wurde das gemeldet, und die cloud-init-Entwickler haben entschieden, dass das so bleibt; geändert wurde nur der Kommentar in der Hauptdatei. Mit der Datei 10-homelab.conf aus Schritt 3 liegt dein Zettel vor ihrem, deshalb ist die Anleitung so gebaut. Nachsehen kannst du mit ls /etc/ssh/sshd_config.d/, und die Wahrheit sagt immer sudo sshd -T.
Debian 12: „Have not found any log file for sshd jail." fail2ban startet nicht. In den Issues auf GitHub der häufigste Debian-12-Treffer: Bookworm schreibt ohne rsyslog keine auth.log mehr, und die Paketversion 1.0.2 sucht trotzdem danach. Die Zeile backend = systemd aus Schritt 4 löst es. Fehlt danach noch das Modul systemd im Fehlertext, fehlt das Paket python3-systemd: sudo apt install python3-systemd. In Debian 13 ist es eine feste Abhängigkeit, dort tritt beides nicht mehr auf.
„Schlüssel hinterlegt, fragt trotzdem nach Passwort." Fast immer Rechte. Die Manpage von sshd ist da streng: Ist das Home-Verzeichnis, ~/.ssh oder authorized_keys für andere schreibbar, ignoriert der Server die Datei stillschweigend. Die beiden chmod aus Schritt 2 (700 für den Ordner, 600 für die Datei) reparieren das. Der Grund steht im Journal: sudo journalctl -u ssh -n 20 zeigt eine Zeile mit „Authentication refused: bad ownership or modes".
Ausgesperrt. Passiert, wenn man Schritt 3 ohne zweites Fenster durchzieht. Dann bleibt der Weg, der nicht über SSH geht: Proxmox-Konsole, Monitor, Hoster-Konsole. Dort anmelden, die Datei 10-homelab.conf löschen oder PasswordAuthentication yes hineinschreiben, sudo systemctl restart ssh, und von vorn, diesmal mit Testfenster.
Reicht das?
Für einen Homelab-Server, ja. Schlüssel statt Passwort nimmt den Bots ihre einzige Methode, root aussperren nimmt ihnen ihr Lieblingsziel, fail2ban räumt das Protokoll auf. Mehr Härtung, etwa 2FA über PAM oder Anmeldung nur aus dem VPN, bringt für einen Server hinter dem Heimrouter kaum zusätzliche Sicherheit, aber zusätzliche Wege, sich auszusperren. Ich würde es lassen, bis der Server wirklich von außen erreichbar ist. Und wenn du so weit bist, gehört der SSH-Port trotzdem nicht in die Portfreigabe des Routers, sondern hinter ein VPN. Das ist ein eigener Beitrag.
Was mich an dem Thema freut: Die Anleitung ist im Kern seit zehn Jahren gleich, und sie funktioniert immer noch. Es sind nur die Zettel im Ordner dazugekommen. Was hat dich beim Absichern deines ersten Servers am meisten Zeit gekostet, und hast du dich dabei auch schon einmal selbst ausgesperrt? Schreib es in die Kommentare, mit dem Fehler, der dich am Ende weitergebracht hat.
Der Schlüssel liegt jetzt an einer Tür, seine Passphrase gehört in den Passwort-Manager. Wenn du noch keinen eigenen hast: Vaultwarden mit Docker installieren ist der nächste logische Abend.
Quellen
- sshd_config(5): Include, PasswordAuthentication, PermitRootLogin, MaxAuthTries, „first obtained value"
- sshd(8): Optionen -t und -T, Rechte für ~/.ssh und authorized_keys
- ssh-keygen(1): Schlüsseltypen, Passphrase
- ssh-copy-id(1), Debian-Manpage
- Ubuntu Server Docs: OpenSSH server (sshd_config.d, sshd -t, Neustart)
- Debian Wiki: SSH (sshd_config.d seit Bullseye, Empfehlungen)
- Debian-Patch debian-config: Include-Zeile am Anfang von sshd_config
- Launchpad #2088207: cloud-init enables ssh password auth in an unexpected config file
- Ubuntu Discourse: sshd now uses socket-based activation (22.10 and later)
- fail2ban jail.conf: Standardwerte, jail.local, bantime.increment
- fail2ban Issue #3567: „Have not found any log file for sshd jail" auf Debian 12
- fail2ban Discussion #3677: Debian 12, backend und python3-systemd
- Debian trixie: openssh-server 1:10.0p1, fail2ban 1.1.0 (Abhängigkeit python3-systemd)
- Launchpad: openssh in Ubuntu 24.04 (9.6p1) und 26.04 (10.2p1)