Skip to content

Server erstinstallation

Komplett-Walkthrough: vom ersten SSH-Login nach dem Provisioning bis zum produktionsreifen Server. Annahme: Debian 12 (Bookworm) oder Ubuntu 22.04/24.04 LTS. Cloud-VM (Hetzner, DigitalOcean, AWS) oder bare metal — der Ablauf ist identisch.

Quick-Links:


Phase 0: Was du VOR dem ersten Login brauchst

Section titled “Phase 0: Was du VOR dem ersten Login brauchst”
  • Server-IP / Hostname notiert
  • Cloud-Console offen (für Rescue/Konsole, falls SSH versemmelt)
  • SSH-Key-Paar ed25519 auf deinem Workstation erstellt:
    Terminal window
    ssh-keygen -t ed25519 -a 100 -C "deploy@workstation"
  • Public-Key (~/.ssh/id_ed25519.pub) in der Cloud-Console als “SSH-Key” hinterlegt
  • 30 Minuten Zeit (kein Hot-Patch-Mode)

Faustregel: Wenn du nach dem ersten Login direkt apt update machst, ohne User/SSH/Firewall gehärtet zu haben, hast du einen unsicheren Server. Erst härten, dann Dienste draufpacken.

Cloud-Console → Cloud-Provider gibt dir initial nur root mit Passwort. Das schließen wir sofort.

Terminal window
# Lokal: erste SSH-Verbindung (Passwort-Auth, nur dieses eine Mal)
ssh root@<server-ip>
# System auf Stand bringen
apt update && apt full-upgrade -y
reboot # falls Kernel-Update
# wieder einloggen
Terminal window
# User anlegen
adduser deploy
usermod -aG sudo deploy
# sudo ohne Passwort für deploy (optional, oft sinnvoll bei Automation)
# Achtung: ohne Passwort = bequemer, aber wenn Key kompromittiert = game over.
# Besser: Passwort-Prompt behalten.
echo "deploy ALL=(ALL) ALL" > /etc/sudoers.d/deploy
# SSH-Key vom Workstation auf den Server übertragen
# Auf der Workstation (NICHT auf dem Server):
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@<server-ip>
# Test: Login als deploy, ohne root
ssh deploy@<server-ip>
sudo whoami # muss "root" ausgeben
Terminal window
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sudo nano /etc/ssh/sshd_config

Folgende Werte setzen (suchen mit /, ändern mit i, speichern mit :wq):

PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
X11Forwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
MaxAuthTries 3
LoginGraceTime 30
AllowUsers deploy

Optional, wenn statische IP / WireGuard:

Port 2222 # nur Security-through-Obscurity, nicht alleine verlassen

Vor dem Restart: zweite SSH-Session offen lassen! Falls die Config kaputt ist, kannst du sie in Session #1 reparieren ohne KVM/Rescue.

Terminal window
# Config-Syntax checken
sudo sshd -t
# Restart
sudo systemctl restart sshd
# In Session #2 testen: NEUE SSH-Verbindung als deploy@server
# Funktioniert? → alten root-Login killen, Session #1 schließen.
# Funktioniert nicht? → KVM/Rescue, sshd_config.bak zurückspielen.

Standard-Policy: alles dicht, nur was explizit erlaubt ist.

Terminal window
sudo apt install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
# SSH (Port 22 oder den geänderten Port)
sudo ufw allow 22/tcp # oder 2222/tcp
# Web (falls absehbar)
sudo ufw allow 80,443/tcp
sudo ufw enable
sudo ufw status verbose
# Status: active, Logging: on (low), Default: deny (incoming), allow (outgoing)

Vergiss nicht: wenn du ufw vor dem SSH-Config-Update aktivierst und SSH auf 2222 änderst, sperrst du dich aus.

Terminal window
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
# Dialog: "Yes" → automatische Security-Updates
# Bei Servern mit kritischen Diensten: Auto-Reboot aktivieren
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
# Unattended-Upgrade::Automatic-Reboot "true";
# Unattended-Upgrade::Automatic-Reboot-Time "04:00";
# Status prüfen
sudo unattended-upgrade --dry-run --debug
Terminal window
sudo apt install fail2ban
sudo systemctl enable --now fail2ban
# Eigene Jail-Config (überschreibt /etc/fail2ban/jail.conf)
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
backend = systemd
Terminal window
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
Terminal window
sudo hostnamectl set-hostname server-01.example.com
sudo timedatectl set-timezone Europe/Berlin
sudo apt install systemd-timesyncd
sudo systemctl enable --now systemd-timesyncd
timedatectl status
# "System clock synchronized: yes"
# "NTP service: active"
# Locale
sudo apt install locales
sudo locale-gen en_US.UTF-8 de_DE.UTF-8
sudo update-locale LANG=de_DE.UTF-8
Terminal window
sudo apt install -y \
curl \
wget \
git \
vim \
htop \
iotop \
net-tools \
dnsutils \
ca-certificates \
gnupg \
rsync \
unattended-upgrades \
fail2ban \
ufw \
restic \
mtr-tiny \
tmux

Nicht später. Backups ohne Restore-Test sind kein Backup. Siehe Server Backups.

Minimum-Variante (Schnellstart):

Terminal window
# Repo initialisieren (Hetzner Storage Box, S3, oder lokal mit zweiter Platte)
restic -r sftp:backup@backup.example.com:/srv/backups init
# Erster Snapshot
restic -r sftp:backup@backup.example.com:/srv/backups backup \
/etc /home /var/log /root
# Restore üben (in /tmp/restore-test, nicht live!)
restic -r sftp:backup@backup.example.com:/srv/backups restore latest \
--target /tmp/restore-test
ls /tmp/restore-test/etc
# Wenn das Verzeichnis da ist und Inhalt hat → Backup funktioniert.
# Cron: täglich 03:00
crontab -e
# 0 3 * * * restic -r sftp:backup@backup.example.com:/srv/backups backup /etc /home /var/log /root --quiet

Bevor du Dienste draufpackst, ein Snapshot des Cloud-Providers. Falls etwas schiefgeht, kannst du in 30 Sekunden zurückrollen.

Terminal window
# Cloud-Console → Snapshots/Images → "create snapshot"
# Namen: "fresh-hardenend-2026-07-02"

Notiere in deiner eigenen Doku:

  • Server-IP
  • SSH-Port (Standard oder geändert)
  • User-Namen + Berechtigungen
  • Offene Ports
  • Backup-Repo-URL + Cron-Zeit
  • Snapshot-ID
  • Cloud-Provider-Account / Projekt-ID

Erst jetzt. Beispiel: Docker.

Terminal window
# Docker installieren (Debian-Pakete, nicht snap)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker deploy
# Login als deploy neu starten (damit docker-Gruppe greift)
# Test
docker run --rm hello-world

Reverse Proxy / Webhosting: Nginx Proxy Manager.


Problem Ursache Fix
Nach ufw enable sofort ausgesperrt ufw aktiviert, aber SSH nicht in Allow-Liste KVM/Rescue → ufw allow 22/tcp && ufw reload
sshd_config kaputt, Restart schlägt fehl Tippfehler / fehlender AllowUsers-Eintrag KVM/Rescue → cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config && systemctl restart sshd
apt update mit “no public key”-Fehler Hetzner-Images haben veraltete Keys sudo apt-key adv --refresh-keys oder Repos umstellen auf signed-by=
sudo whoami → “Sorry, try again” deploy nicht in sudo-Gruppe, oder /etc/sudoers.d/ falsch usermod -aG sudo deploy, dann neu einloggen
Passwort-Login klappt nicht PasswordAuthentication no gesetzt, aber kein Key hinterlegt KVM/Rescue → SSH-Key manuell in /home/deploy/.ssh/authorized_keys eintragen
systemd-timesyncd synchronisiert nicht Server in Cloud hat NTP-Bug (manche Hetzner-Images) apt install chrony && systemctl enable chrony

  • Server härten — wenn du später was nachziehen willst
  • Server Backups — Restore-Strategie, Retention, Monitoring
  • Nginx Proxy Manager — sobald die erste Domain drauf soll