> ## Content Index
> Fetch the complete content index at: https://blog.cube-m.de/llms.txt
> Use this file to discover other available public pages before exploring further.

# 02. Debian 13 auf einem VPS sicher einrichten
- URL: https://blog.cube-m.de/debian-13-auf-einem-vps-sicher-einrichten-ssh-key-erster-login-und-solide-basis/
- Published: 2026-01-08T11:01:51.000Z
- Updated: 2026-06-24T12:50:12.000Z
- Description: Debian 13 auf einem VPS sauber und sicher einrichten: SSH nur per ed25519-Key, erster Login verständlich erklärt, Updates & Basispakete, Zeitzone/Locale, SSH-Hardening und Firewall. Eine stabile Basis, auf der Docker & alle weiteren Dienste sauber aufbauen.
- Author: Martin
- Tags: VPS, Basis, Netcup, Debian, Server-Sicherheit, Server, #Import 2026-09-06 19:52

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:

```bash
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.

```bash
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:

```bash
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:

```bash
# 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:

```bash
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:

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

```

Test – ausloggen und als `martin` einloggen:

```bash
exit
ssh martin@<SERVER-IP>

```

## 4\. Erster SSH-Login – Meldungen verstehen

Von deinem Rechner aus:

```bash
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**

```bash
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:

```bash
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.

```bash
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:

```bash
sudo timedatectl set-timezone Europe/Berlin

```

Locale auf Englisch setzen:

```bash
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:

```bash
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:

```bash
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**

```text
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:

```bash
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:

```bash
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:
> 
> ```bash
> 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:

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

```

**IPv6-Schutz aktivieren**:

```bash
sudo nano /etc/default/ufw

```

Setze (falls nicht schon gesetzt):

```text
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:

```bash
sudo ufw allow OpenSSH

```

Firewall aktivieren:

```bash
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**

```bash
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:

```bash
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:

```bash
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:

```bash
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:

```bash
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**

```bash
groups martin

```

Erscheint `sudo` in der Ausgabe, ist alles korrekt.

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

```bash
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](#)