Im ersten Teil dieser Reihe hast du deinen Server frisch aufgesetzt, einen unprivilegierten Benutzer angelegt und dich per SSH-Key eingeloggt. Jetzt ist es Zeit, den Server so abzusichern, dass er auch im offenen Internet standhält. Ein frisch installiertes Ubuntu oder Debian ist per se kein Sicherheitsrisiko — aber ein unkonfigurierter Server wird innerhalb von Minuten von automatisierten Bots angegriffen. Dieser Teil zeigt dir Schritt für Schritt, wie du mit wenigen, gezielten Maßnahmen den Angriffsvektoren deutlich reduzierst.
Voraussetzungen
- Du hast Teil 1 abgeschlossen: Ubuntu 22.04 LTS (oder Debian 12) läuft, du hast einen Sudo-User, SSH-Key-Login funktioniert.
- Du bist nicht als
rooteingeloggt, sondern als dein unprivilegierter User mitsudo-Rechten. - Du hast eine aktive SSH-Session auf dem Server offen — halte diese während der gesamten Konfiguration offen, bis du alle Änderungen verifiziert hast.
- Optional: Eine zweite Terminal-Session parallel, um dich im Notfall neu einloggen zu können.
Firewall mit ufw einrichten
ufw (Uncomplicated Firewall) ist das empfohlene Frontend für iptables auf Debian-basierten Systemen. Die Konfiguration ist lesbar und schnell erledigt.
Standard-Policy setzen
Die goldene Regel lautet: Alles verbieten, nur explizit erlaubtes zulassen.
# Eingehenden Traffic standardmäßig ablehnen
sudo ufw default deny incoming
# Ausgehenden Traffic standardmäßig erlauben
sudo ufw default allow outgoing
SSH-Port freigeben — BEVOR du ufw aktivierst
Achtung: Aktivierst du ufw, bevor du SSH erlaubst, sperrst du dich selbst aus. Das ist der häufigste Anfängerfehler.
# SSH auf Standard-Port 22 erlauben
sudo ufw allow ssh
# Alternativ, wenn du einen anderen Port verwendest (z.B. 2222):
sudo ufw allow 2222/tcp
# Firewall aktivieren
sudo ufw enable
# Status prüfen
sudo ufw status verbose
Weitere Ports öffnen
| Dienst | Befehl | Anmerkung |
|---|---|---|
| HTTP (80) | sudo ufw allow http | Für Nginx/Apache nötig |
| HTTPS (443) | sudo ufw allow https | TLS-Traffic |
| Postgres (5432) | sudo ufw allow from 10.0.0.0/8 to any port 5432 | Nur aus internem Netz |
| Custom Port | sudo ufw allow 8080/tcp | Explizit TCP oder UDP angeben |
| Port-Range | sudo ufw allow 60000:61000/udp | z.B. für Mosh |
| Quelle einschränken | sudo ufw allow from 203.0.113.0/24 to any port 22 | IP-Whitelist für SSH |
Regeln entfernen: sudo ufw delete allow http oder über die Nummer: sudo ufw delete 3 (Nummer aus sudo ufw status numbered).
SSH weiter härten
Die SSH-Konfiguration liegt in /etc/ssh/sshd_config. Erstelle vorher ein Backup:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sudo nano /etc/ssh/sshd_config
Empfohlene sshd_config-Einstellungen
# /etc/ssh/sshd_config — Relevante Einstellungen
# Nur SSH-Protokoll 2 (Protokoll 1 ist seit Jahren unsicher)
Protocol 2
# Port ändern (optional, siehe Pro/Contra unten)
Port 2222
# Root-Login deaktivieren
PermitRootLogin no
# Passwort-Authentifizierung deaktivieren (nur Keys!)
PasswordAuthentication no
ChallengeResponseAuthentication no
# Leere Passwörter verbieten
PermitEmptyPasswords no
# Nur bestimmte User dürfen sich einloggen
AllowUsers deinuser deployuser
# Login-Versuche begrenzen
MaxAuthTries 3
# Idle-Timeout: Session nach 10 Minuten Inaktivität trennen
ClientAliveInterval 600
ClientAliveCountMax 0
# X11-Forwarding abschalten (braucht kein Server)
X11Forwarding no
# Banner anzeigen (optional, aber sinnvoll)
Banner /etc/issue.net
Nach Änderungen immer die Syntax prüfen und neu laden:
# Syntax-Check — bricht ab ohne neu zu starten
sudo sshd -t
# Nur wenn kein Fehler: neu laden
sudo systemctl reload sshd
SSH-Port ändern — Pro und Contra
Pro:
- Reduziert Log-Spam durch automatisierte Bots massiv (Security through Obscurity als ergänzende Maßnahme)
- Brute-Force-Tools scannen primär Port 22
Contra:
- Kein echter Sicherheitsgewinn gegen gezielte Angriffe (Portscanner finden den Port)
- Erhöht Konfigurationsaufwand (ufw, Monitoring, alle Clients anpassen)
- Manche Firewalls/NAT-Umgebungen blockieren Nicht-Standard-Ports
Empfehlung: Bei einem öffentlichen Server lohnt sich der Port-Wechsel wegen der deutlich ruhigeren Logs. Denk daran, ufw entsprechend anzupassen und den neuen Port zu dokumentieren.
Fail2ban installieren und konfigurieren
Fail2ban überwacht Log-Dateien und sperrt IPs temporär, die zu viele fehlgeschlagene Login-Versuche produzieren.
Installation
sudo apt update
sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
jail.local konfigurieren
Überschreibe nie jail.conf direkt — erstelle stattdessen jail.local:
sudo nano /etc/fail2ban/jail.local
# /etc/fail2ban/jail.local
[DEFAULT]
# IP-Adressen, die niemals gebannt werden (eigene IPs eintragen!)
ignoreip = 127.0.0.1/8 ::1 203.0.113.42
# Beobachtungszeitraum in Sekunden (10 Minuten)
findtime = 600
# Maximale Fehlversuche in findtime
maxretry = 5
# Bann-Dauer in Sekunden (1 Stunde)
bantime = 3600
# Nach wiederholten Banns exponentiell verlängern
bantime.increment = true
bantime.multiplier = 2
[sshd]
enabled = true
port = ssh
# Falls du den SSH-Port geändert hast:
# port = 2222
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
# Fail2ban neu starten und Status prüfen
sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
Eine gebannte IP manuell entsperren: sudo fail2ban-client set sshd unbanip 203.0.113.99
Automatische Security-Updates
Manuelles Einspielen von Updates wird vergessen. unattended-upgrades sorgt dafür, dass kritische Sicherheitspatches automatisch installiert werden.
sudo apt install unattended-upgrades apt-listchanges -y
# Konfiguration aktivieren
sudo dpkg-reconfigure --priority=low unattended-upgrades
Die Konfiguration liegt in /etc/apt/apt.conf.d/50unattended-upgrades. Empfohlene Anpassungen:
# /etc/apt/apt.conf.d/50unattended-upgrades (Auszug)
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
// Optional: auch reguläre Updates
// "${distro_id}:${distro_codename}-updates";
};
// Automatisch neu starten, wenn Kernel-Update es erfordert
Unattended-Upgrade::Automatic-Reboot "false";
// E-Mail bei Problemen (optional)
Unattended-Upgrade::Mail "deine@email.de";
Unattended-Upgrade::MailReport "on-change";
Hinweis:
Automatic-Reboot "false"ist für Produktionsserver oft sinnvoll — du willst den Neustart selbst kontrollieren. Prüfe regelmäßig mitsudo needrestart, ob ein Reboot aussteht.
Trockenlauf testen: sudo unattended-upgrades --dry-run --debug
Unnötige Dienste abschalten
Das Prinzip der geringsten Angriffsfläche: Jeder laufende Dienst ist ein potenzieller Angriffspunkt.
# Alle laufenden Dienste anzeigen
sudo systemctl list-units --type=service --state=running
# Beispiel: avahi-daemon (mDNS, auf Servern selten gebraucht)
sudo systemctl stop avahi-daemon
sudo systemctl disable avahi-daemon
# Lauschende Ports anzeigen — was ist überhaupt offen?
sudo ss -tulnp
# Installierte, aber nicht benötigte Pakete entfernen
sudo apt autoremove --purge
Typische Kandidaten zum Deaktivieren auf einem Minimal-Server:
avahi-daemon— mDNS/Bonjour, für Server unnötigcups— Druckdienstbluetooth— falls kein Bluetooth-Hardware vorhandenModemManager— für reine Server-Setups nicht nötig
Logs prüfen und auswerten
Ein gesicherter Server ist nur so gut wie dein Monitoring. Prüfe regelmäßig die Logs auf verdächtige Aktivitäten.
journalctl — der zentrale Log-Einstieg
# Alle Kernel- und Systemlogs der letzten Stunde
sudo journalctl --since "1 hour ago"
# Nur SSH-Logs live verfolgen
sudo journalctl -u sshd -f
# Fehlgeschlagene Login-Versuche der letzten 24h
sudo journalctl -u sshd --since "24 hours ago" | grep "Failed password"
# Sudo-Nutzung auswerten
sudo journalctl _COMM=sudo --since today
/var/log/auth.log
# Fehlgeschlagene Logins
sudo grep "Failed" /var/log/auth.log | tail -50
# Erfolgreiche Logins
sudo grep "Accepted" /var/log/auth.log | tail -20
# Top-IPs nach Angriffsversuchen
sudo grep "Failed password" /var/log/auth.log | \
awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20
Optionales Monitoring-Tool: logwatch
sudo apt install logwatch -y
# Täglichen Report per E-Mail: /etc/logwatch/conf/logwatch.conf anpassen
Optional: Zwei-Faktor-Authentifizierung für SSH
Für besonders kritische Server kannst du TOTP (Time-based One-Time Password, z.B. Google Authenticator oder Authy) als zweiten Faktor für SSH einrichten.
sudo apt install libpam-google-authenticator -y
# Als der User, der 2FA nutzen soll, ausführen (nicht als root):
google-authenticator
Beantworte die Fragen (zeitbasiert: ja, Notfall-Codes speichern, Rate-Limiting: ja). Dann PAM und sshd anpassen:
# /etc/pam.d/sshd — am Anfang der Datei einfügen:
# auth required pam_google_authenticator.so
# /etc/ssh/sshd_config — anpassen:
# ChallengeResponseAuthentication yes
# AuthenticationMethods publickey,keyboard-interactive
Achtung: Teste 2FA immer in einer zweiten SSH-Session, bevor du die erste schließt. Ein Konfigurationsfehler hier kann dich dauerhaft aussperren.
Häufige Fehler
Diese Stolperfallen begegnen nahezu jedem, der einen Server zum ersten Mal absichert:
1. ufw aktivieren vor SSH-Freigabe
Der Klassiker. Lösung: SSH immer als erstes freigeben (sudo ufw allow ssh), erst dann sudo ufw enable. Bei einem Managed-Server gibt es oft eine Web-Konsole als Rettungsanker.
2. SSH-Port in ufw vergessen nach Port-Wechsel
Du änderst SSH auf Port 2222, aktivierst ufw — und vergisst, Port 2222 freizugeben. Port 22 ist noch offen, dein neuer Port nicht. Prüfe nach jedem Port-Wechsel: sudo ufw status numbered.
3. sshd_config-Syntaxfehler ohne vorherigen Test
Direktes Neustart von sshd nach einer fehlerhaften Konfiguration kann die SSH-Verbindung unterbrechen. Immer zuerst sudo sshd -t ausführen.
4. Eigene IP nicht in fail2ban ignoreip eintragen
Du tippst dreimal das falsche Passwort ein (vielleicht beim Testen) und sperrst dich selbst aus. Trage deine feste IP in ignoreip ein.
5. PasswordAuthentication abschalten ohne SSH-Key-Test Du deaktivierst Passwort-Login, hast aber den SSH-Key-Login nicht vorher getestet. Teste Key-Login immer in einer separaten Session, bevor du Passwort-Auth deaktivierst.
6. unattended-upgrades installiert, aber nie auf Reboot-Notwendigkeit geprüft
Security-Updates werden eingespielt, aber Dienste laufen noch mit alten Binaries. sudo needrestart oder sudo systemctl list-units --failed regelmäßig prüfen.
Nächste Schritte
Dein Server ist jetzt solide abgesichert. Im dritten und letzten Teil der Reihe geht es ans Eingemachte: Dienste und Deployment. Wir richten Nginx als Reverse Proxy ein, sichern Traffic mit TLS über Let's Encrypt (Certbot), führen Docker-Container in einer produktionstauglichen Umgebung aus, verwalten Dienste mit systemd-Units und setzen automatisierte Backups mit restic auf. Außerdem schauen wir uns an, wie du Deployments mit GitHub Actions oder einfachen Shell-Skripten automatisierst — sodass ein git push ausreicht, um deine App zu aktualisieren.