Ein frisch installierter VPS ist schnell verfügbar. Das heißt aber noch nicht, dass er sinnvoll oder sicher eingerichtet ist. In den ersten Minuten nach der Installation legst du fest, ob dein System später ruhig wartbar bleibt – oder ob du dir mit kleinen Versäumnissen dauerhaft Komplexität einhandelst.

Dieser Beitrag baut bewusst nur das Fundament: Debian 13 installieren, SSH per Schlüssel einrichten und eine saubere Linux-Basis herstellen. Keine Anwendungen, kein Docker, kein Webserver – das kommt in den nächsten Artikeln. Diese Basis soll später möglichst unverändert bleiben.

Am Ende hast du

  • Debian 13 (Trixie) installiert und aktualisiert.
  • Einen normalen Benutzer eingerichtet (Beispiel: martin) mit UID 1000
  • SSH-Zugriff ohne Passwort, nur per Schlüssel.
  • Verstanden, was beim ersten SSH-Login passiert (Host-Key, Passphrase).
  • Basispakete installiert, Zeitzone und Locale gesetzt
  • SSH gehärtet, Firewall aktiviert, automatische Sicherheitsupdates konfiguriert

Voraussetzungen

  • VPS mit öffentlicher IPv4.

  • Zugriff auf das Provider-Panel (Installation, SSH-Key-Hinterlegung).

  • macOS, Linux oder Windows mit Terminal / SSH-Client.

1. SSH-Key lokal erstellen (ed25519)

SSH arbeitet mit einem Schlüsselpaar:

  • Private Key: bleibt auf deinem Rechner. Er ist dein Zugang.

  • Public Key: wird auf dem Server hinterlegt. Er erlaubt genau deinem Private Key den Login.

Das ist der entscheidende Unterschied zu Passwörtern: Der Server muss kein Passwort akzeptieren, das jemand erraten könnte. Er akzeptiert nur den Nachweis, dass du den passenden Schlüssel besitzt.

Key erzeugen

Auf deinem lokalen Rechner:

ssh-keygen -t ed25519 -a 100 -C "martin@homelab"
  • Speicherort: ENTER drücken (Standard ist sinnvoll).
  • Passphrase: unbedingt setzen

Warum eine Passphrase wichtig ist

Ein SSH-Key ohne Passphrase ist im schlimmsten Fall sofort nutzbar, wenn die Datei irgendwo kopiert wird – z. B. durch ein kompromittiertes Backup. Eine Passphrase ist die zweite Schutzschicht.

Danach existieren:

  • ~/.ssh/id_ed25519 (Private Key – niemals teilen!)
  • ~/.ssh/id_ed25519.pub (Public Key – dieser kommt auf den Server)

Check Prüfen ob beide Dateien existieren.

ls -l ~/.ssh/id_ed25519*

2. Public Key ≠ Fingerprint

Hier passieren die meisten Anfängerfehler, weil die Begriffe ähnlich wirken.

Fingerprint (nicht für Login geeignet)

Beispiel:

SHA256:abc123def456...

Das ist nur eine Prüfsumme zur Kontrolle. Damit loggst du dich nicht ein.

Public Key (das, was der Server braucht)

Den Public Key gibst du ins Provider-Panel oder kopierst ihn auf den Server:

cat ~/.ssh/id_ed25519.pub

Er beginnt typischerweise mit:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... martin@homelab

Merksatz

Ein SSH-Key beginnt mit ssh-ed25519. Alles, was mit SHA256: beginnt, ist ein Fingerprint, kein Login-Key.

Check

Wenn deine Ausgabe mit ssh-ed25519 beginnt, hast du den richtigen Inhalt zum Hinterlegen.

3. Debian 13 installieren

Option A: Provider mit SSH-Key-Support (z. B. Netcup, Hetzner):

  1. Unter Medien Debian 13 auswählen
  2. Partitionen: eine große Partition für das Betriebssystem, die den kompletten freien Speicherplatz beinhaltet
  3. Hostname: homelab
  4. Zusätzlichen Benutzer erstellen aktivieren (Benutzername z. B. martin, dazu ein Passwort wählen)
  5. Deinen SSH-Key hinterlegen/auswählen
  6. Passwort-Login für SSH deaktivieren (falls angeboten)
  7. Keine Extras, keine Skripte
  8. Auf Weiter klicken

Option B: Provider ohne SSH-Key-Support

  1. Debian 13 (Trixie) installieren
  2. Per Root-Passwort einloggen (wird vom Provider generiert)
  3. Manuell User anlegen und SSH-Key kopieren (siehe unten)

Nach Installation: User anlegen (falls nicht vom Provider gemacht)

Falls dein Provider keinen User angelegt hat, auf dem Server als root:

# Als root einloggen
ssh root@<SERVER-IP>

# User erstellen (UID 1000 wird automatisch vergeben, wenn erster User)
adduser martin

# Sudo-Rechte geben
usermod -aG sudo martin

# SSH-Verzeichnis anlegen
mkdir -p /home/martin/.ssh
chmod 700 /home/martin/.ssh

Public Key kopieren – diesen Befehl führst du auf deinem lokalen Rechner aus:

cat ~/.ssh/id_ed25519.pub | ssh root@<SERVER-IP> "cat >> /home/martin/.ssh/authorized_keys"

Anschließend wieder als root auf dem Server – Rechte korrekt setzen:

chmod 600 /home/martin/.ssh/authorized_keys
chown -R martin:martin /home/martin/.ssh

Test – ausloggen und als martin einloggen:

exit
ssh martin@<SERVER-IP>

4. Erster SSH-Login – Meldungen verstehen

Von deinem Rechner aus:

ssh martin@<SERVER-IP>

4.1 Host-Key-Abfrage

Beim ersten Login erscheint:

The authenticity of host '...' can't be established.
ED25519 key fingerprint is SHA256:...
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

Was passiert hier?

SSH kennt diesen Server noch nicht. Der Server präsentiert seinen Host-Key. Dein Client fragt dich, ob du diesem Schlüssel vertraust. Das schützt vor Man-in-the-Middle-Angriffen.

Wenn du den Server gerade selbst installiert hast, bestätige mit yes.

Danach steht oft:

Warning: Permanently added '...' to the list of known hosts.

Der Host-Key wird lokal gespeichert: ~/.ssh/known_hosts.

4.2 Passphrase-Abfrage

Wenn du eine Passphrase gesetzt hast:

Enter passphrase for key '.../id_ed25519':

Was bedeutet das?

  • Du nutzt Schlüssel-Login.
  • Dein Private Key ist zusätzlich geschützt.
  • Du gibst keinen Server-Login per Passwort frei.

Beim zweiten Login kommt die Host-Key-Abfrage nicht erneut (solange der Server gleich bleibt).

5. Benutzer und UID 1000

Nachdem der erste Login funktioniert, prüfen wir die Benutzer- und Rechtebasis.

Best Practice: Der primäre Admin-User (im Beispiel martin) sollte die Standard-UID/GID 1000:1000 haben.

Warum das wichtig ist

Viele Container- und NAS-Workflows arbeiten mit PUID/PGID. Wenn dein Standardbenutzer konsistent 1000:1000 ist, werden Dateirechte bei Volumes, Backups und Migrationen deutlich einfacher.

Check

whoami
id martin

Erwartung: UID und GID sind beide 1000:

martin
uid=1000(martin) gid=1000(martin) groups=1000(martin)

Hinweis zur sudo-Gruppe

Ob 27(sudo) in der Ausgabe erscheint, hängt vom Provider ab: Manche Installer legen den User direkt mit Sudo-Rechten an (z. B. Netcup), andere nicht. Falls die Gruppe fehlt, holen wir das in Sektion 7 nach – wichtig ist hier nur, dass UID und GID stimmen.

6. Die fünf Basisschritte

Schritt 1: System aktualisieren & Basispakete installieren

Zuerst Updates:

sudo apt update && sudo apt upgrade -y

Basispakete
Ein frisch installiertes Debian ist sehr minimal gehalten. Das ist gut für Sicherheit, aber im Alltag fehlen ein paar Werkzeuge, die wir für Administration, Fehlersuche und spätere Installationen ständig brauchen.

sudo apt install -y curl ca-certificates gnupg nano htop git unzip rsync bash-completion needrestart
Paket Zweck
curl HTTP(S)-Requests, Downloads, API-Checks
ca-certificates Vertrauenswürdige Zertifikate für HTTPS/TLS
gnupg Signaturen prüfen (z. B. bei externen Quellen)
nano Konfigurationsdateien bearbeiten
htop Prozesse und Ressourcen übersichtlich prüfen
git Versionierung für Doku und Configs
unzip ZIP-Archive entpacken
rsync Effiziente Backups und Syncs
bash-completion Weniger Tippfehler, bessere CLI-Erfahrung
needrestart Zeigt nach Updates an, welche Dienste neu gestartet werden müssen – wichtig für unattended-upgrades

Schritt 2: Zeitzone und Locale prüfen

Zeitzone setzen:

sudo timedatectl set-timezone Europe/Berlin

Locale auf Englisch setzen:

sudo update-locale LANG=en_US.UTF-8

Wichtig

Damit die Sprachänderung aktiv wird, musst du dich einmal abmelden und neu per SSH anmelden.

Nach erneutem Login prüfen:

timedatectl
locale

Warum das wichtig ist

Zeit ist Grundlage für Logs und Debugging. Ein sauber gesetztes Locale verhindert später Probleme mit Encoding, Sonderzeichen und konsolenbasierten Tools.

Warum Englisch als Systemsprache

Für Server und SSH-basierte Systeme ist en_US.UTF-8 Best Practice – auch in deutschsprachigen Teams. Der Grund ist praktisch: Fehlermeldungen, Logs und Dokumentation sind auf Englisch deutlich besser dokumentiert und recherchierbar. Wenn du später eine Fehlermeldung googelst oder in einem Forum postest, findest du mit englischen Systemmeldungen schneller Hilfe. Das Tastaturlayout wird davon nicht beeinflusst – deine Eingaben per SSH kommen vom Client und bleiben unverändert.

Check

timedatectl zeigt die richtige Zeitzone. locale zeigt LANG=en_US.UTF-8.

Schritt 3: SSH weiter absichern

Konfiguration öffnen:

sudo nano /etc/ssh/sshd_config

Diese Datei steuert, wie sich der SSH-Server verhält. Die folgenden Einstellungen sorgen dafür, dass nur das Nötigste erlaubt ist.

Empfohlene Kerneinstellungen

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AllowUsers martin
Einstellung Bedeutung
PermitRootLogin no Niemand kann sich direkt als Root anmelden. Selbst wenn jemand einen Root-Schlüssel hätte, wäre der Login blockiert.
PasswordAuthentication no Der Server akzeptiert keine Passwörter mehr. Damit fallen die meisten automatisierten Angriffe weg.
PubkeyAuthentication yes Anmeldung ist nur noch mit SSH-Schlüsseln erlaubt – also mit deinem Key.
AllowUsers martin Nur der Benutzer martin darf sich per SSH anmelden. Selbst wenn später weitere Nutzer existieren, kommen sie nicht automatisch per SSH rein.

Nach der Änderung nano mit control + x beenden und mit Y speichern bestätigen.

Vor dem Reload: Syntax prüfen

Bevor du den Dienst neu lädst, prüfst du die Konfiguration auf Tippfehler:

sudo sshd -t

Wenn der Befehl ohne Ausgabe zurückkehrt, ist die Syntax in Ordnung. Bei Fehlern wirst du auf die Zeile hingewiesen – korrigieren, bevor du weitermachst.

Aktivieren:

sudo systemctl reload ssh

Warum das wichtig ist:

  • Root-Login über SSH ist ein unnötiges Risiko
  • Passwort-Login ist die häufigste Angriffsfläche bei VPS-Systemen
  • Weniger SSH-Features bedeutet weniger Überraschungen

⚠️ Wichtiger Check – aussperren vermeiden

Öffne ein zweites Terminal und teste den Login erneut, bevor du die alte Session schließt:

ssh martin@<SERVER-IP>

Erwartung: Login klappt. Ein Login mit ssh root@<SERVER-IP> wird sofort verweigert. Erst danach die alte Session schließen.

Schritt 4: Firewall aktivieren (UFW, minimal offen)

UFW installieren und Default-Policy setzen:

sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing

IPv6-Schutz aktivieren:

sudo nano /etc/default/ufw

Setze (falls nicht schon gesetzt):

IPV6=yes

Speichern (Ctrl+X, dann Y).

Hintergrund:
Viele VPS haben IPv6. Ohne explizite Konfiguration schützt UFW möglicherweise nur IPv4 – IPv6-Verbindungen könnten dann ungeschützt bleiben.

SSH erlauben:

sudo ufw allow OpenSSH

Firewall aktivieren:

sudo ufw enable

UFW warnt, dass bestehende SSH-Verbindungen betroffen sein könnten. Da wir SSH oben explizit erlaubt haben, kannst du mit y bestätigen:

Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

Warum OpenSSH und nicht 22/tcp?

UFW kennt für viele Dienste vordefinierte „Application Profiles". OpenSSH ist eines davon. Funktional ist es bei SSH dasselbe wie 22/tcp, aber die Schreibweise ist klarer und konsistent mit Profilen wie Nginx Full, die später für Web-Dienste sinnvoll werden.

Warum Firewall überhaupt?

Prinzip: Alles ist gesperrt – nur explizit erlaubte Ports sind offen. So behältst du die Kontrolle, auch wenn später Dienste installiert werden.

Warum UFW und nicht nftables direkt?

UFW nutzt unter der Haube nftables (auf Debian 13), bietet aber eine deutlich einfachere Syntax für Homelab-Szenarien. Für komplexe Enterprise-Setups wäre nftables direkt besser – für Self-Hosting ist UFW perfekt.

Check

sudo ufw status verbose

Erwartung:

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp (OpenSSH)           ALLOW IN    Anywhere
22/tcp (OpenSSH (v6))      ALLOW IN    Anywhere (v6)

Schritt 5: Automatische Sicherheitsupdates aktivieren

Debian sollte automatisch Sicherheits-Patches installieren:

sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades

Der Dialog fragt, ob du es aktivieren willst – bestätige mit „Yes".

Was passiert?

  • Täglich werden Sicherheitsupdates automatisch heruntergeladen und installiert
  • Nur aus dem Debian-Security-Repository (keine Breaking Changes)
  • Logs liegen unter /var/log/unattended-upgrades/

Automatischen Reboot konfigurieren

Manche Updates – Kernel, Glibc, systemd – werden erst nach einem Reboot wirksam. Ohne automatischen Reboot bleibt dein Server bis zum manuellen Neustart mit der alten, möglicherweise verwundbaren Version laufen.

Die Standard-Datei /etc/apt/apt.conf.d/50unattended-upgrades ist lang, komplett auf Englisch und alle relevanten Zeilen sind auskommentiert. Statt darin die richtigen Stellen zu suchen und zu entkommentieren, legen wir eine eigene kleine Datei an, die nur unsere Einstellungen enthält:

sudo nano /etc/apt/apt.conf.d/52unattended-upgrades-local

Folgenden Inhalt einfügen:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";

Speichern mit Ctrl+X, dann Y.

Warum eine eigene Datei?

Die höhere Nummer (52) gewinnt gegen die Defaults in der 50-Datei – APT liest die Dateien in /etc/apt/apt.conf.d/ der Reihe nach, spätere überschreiben frühere. Du musst nichts suchen, nichts entkommentieren. Und bei einem Paket-Update der 50-Datei bleibt deine Einstellung erhalten, weil sie in einer separaten Datei steht.

Wann lieber false setzen?

Wenn du echte Mission-Critical-Dienste betreibst, bei denen ungeplante Reboots problematisch wären. Für klassische Self-Hosting-Setups (Vaultwarden, Immich, Blog) ist true mit nächtlichem Zeitfenster die ehrlichere Wahl: Sicherheitslücken werden zeitnah geschlossen, Downtime fällt mitten in die Nacht. needrestart (haben wir oben mit installiert) zeigt dir auf jedem Login an, ob ein Reboot nötig ist – Überraschungen ausgeschlossen.

Check

Zuerst prüfen, ob der Service läuft und beim Boot startet:

sudo systemctl status unattended-upgrades
sudo systemctl is-enabled unattended-upgrades

Erwartung: active (running) und enabled.

Hinweis: Der Service heißt vollständig „Unattended Upgrades Shutdown" – das ist normal und hat nichts mit Herunterfahren zu tun. Es ist ein Wächter-Prozess, der im Hintergrund läuft und beim Systemstart-/Shutdown noch ausstehende Updates sauber abschließt. Die täglichen Updates selbst laufen über einen separaten Timer.

Dann prüfen, ob deine Reboot-Einstellungen wirklich greifen:

apt-config dump | grep -i "Unattended-Upgrade::Automatic-Reboot"

Erwartung:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";

Tauchen beide Zeilen auf, wird deine 52unattended-upgrades-local korrekt eingelesen und überschreibt die Defaults.

Warum das wichtig ist

Sicherheitsupdates manuell zu machen heißt: vergessen. Automatisierung ist 2026 Standard für VPS-Betrieb.

7. sudo statt Root: das Least-Privilege-Prinzip

In den Schritten oben hast du schon mehrmals sudo benutzt. Hier nochmal kurz, warum das so läuft – und wie du sicherstellst, dass es bei dir auch wirklich funktioniert.

Warum nicht einfach als root arbeiten?

Der Root-Benutzer hat uneingeschränkte Systemrechte. Ein Tippfehler – etwa ein rm -rf an der falschen Stelle – kann das ganze System unbrauchbar machen. Deshalb gilt im Server-Alltag das Prinzip Least Privilege: Du arbeitest standardmäßig als normaler Benutzer und holst dir Administratorrechte nur dann, wenn du sie wirklich brauchst – über sudo.

Das ist nicht nur Sicherheits-Hygiene, sondern auch ein Konzept, das später überall wieder auftaucht: Container laufen mit eingeschränkten Rechten, Apps haben eigene Service-User, Backups schreiben mit minimalen Berechtigungen. Wer das Prinzip einmal verinnerlicht hat, versteht später vieles automatisch mit.

Prüfen, ob dein Benutzer in der sudo-Gruppe ist

groups martin

Erscheint sudo in der Ausgabe, ist alles korrekt.

Falls sudo fehlt (einmalig als Root oder über die Provider-Konsole):

sudo usermod -aG sudo martin

Danach einmal neu einloggen – Gruppenrechte werden erst beim Login geladen.

Hinweis

Auf manchen Providern (z. B. Netcup) wird der erste User direkt mit Sudo-Rechten angelegt – dann ist hier nichts zu tun. Auf anderen musst du nachhelfen. Der Check oben zeigt dir, was zutrifft.

Und jetzt?

Du hast jetzt eine saubere, gehärtete Linux-Basis: SSH per Schlüssel, Firewall aktiv, automatische Sicherheitsupdates, klare Benutzerrechte. Diese Basis bleibt von hier an möglichst unverändert – alles, was du an Diensten brauchst, läuft ab dem nächsten Artikel in Containern darüber – Docker und Docker Compose sind das nächste Fundament-Stück.

Was kommt als Nächstes für die Sicherheit?

Aktuell ist Port 22 öffentlich erreichbar – durch die Key-Authentifizierung sind Brute-Force-Versuche zwar chancenlos, aber er ist eben sichtbar. In Artikel 5 (Tailscale VPN) machen wir den letzten Schritt für SSH: Es ist dann nur noch über das verschlüsselte Tailscale-Netzwerk erreichbar, Port 22 verschwindet komplett aus dem öffentlichen Internet.

Für deine Web-Dienste, die später öffentlich erreichbar sein sollen (z. B. Vaultwarden, Immich, Ghost), kommt in Artikel 10 CrowdSec dazu – das übernimmt Angriffserkennung über alle Apps hinweg. Damit du dafür später nicht alles umbauen musst, wird Caddy in dieser Serie von Anfang an mit eingebautem CrowdSec-Plugin installiert (inaktiv – aktiviert wird es erst in Artikel 10).

👉 Nächster Artikel: 03. Docker & Docker Compose Grundlagen