In den ersten beiden Teilen dieser Reihe hast du deinen Linux-Server aufgesetzt und gehärtet. Jetzt wird es ernst: Im dritten und letzten Teil deployen wir echte Dienste. Du lernst, wie du Nginx als Reverse Proxy konfigurierst, Docker-Anwendungen per Compose betreibst, HTTPS mit Let's Encrypt automatisierst, eigene Prozesse als systemd-Services verwaltest und deinen Server grundlegend überwachst. Am Ende steht ein produktionsreifer Stack, den du für eigene Projekte direkt nutzen kannst.
Voraussetzungen
Bevor du weitermachst, stelle sicher, dass folgendes vorhanden ist:
- Ein gehärteter Ubuntu 22.04 / Debian 12 Server (aus Teil 1 und Teil 2)
- SSH-Zugang als nicht-root-Benutzer mit
sudo-Rechten - Eine Domain, die per A-Record auf die Server-IP zeigt (für TLS zwingend)
- UFW-Firewall aktiv, Ports 22, 80 und 443 geöffnet
# Ports prüfen (aus Teil 2)
sudo ufw status
# Sicherstellen, dass Port 80 und 443 offen sind
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Nginx als Reverse Proxy
Installation
Nginx ist in den Standard-Paketquellen beider Distributionen enthalten. Die Installation ist unkompliziert:
sudo apt update && sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx
Nach der Installation erreichst du die Default-Seite über http://DEINE-IP. Gut — der Webserver läuft.
Server Block anlegen
Nginx arbeitet mit sogenannten Server Blocks (das Äquivalent zu Apache Virtual Hosts). Lege für jede Domain eine eigene Konfigurationsdatei an:
sudo nano /etc/nginx/sites-available/meine-app.conf
server {
listen 80;
listen [::]:80;
server_name meine-app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
Config aktivieren und Nginx neu laden:
sudo ln -s /etc/nginx/sites-available/meine-app.conf /etc/nginx/sites-enabled/
sudo nginx -t # Syntax prüfen — immer machen!
sudo systemctl reload nginx
Hinweis:
nginx -tvor jedem Reload ausführen. Ein Syntaxfehler legt sonst den gesamten Webserver lahm.
Docker & Docker Compose installieren
Docker via offizielles Skript
Für Produktionsserver empfiehlt sich das offizielle Docker-Repository statt der oft veralteten Paketquellen:
# Abhängigkeiten
sudo apt install -y ca-certificates curl gnupg
# GPG-Schlüssel und Repo hinzufügen
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Installation
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Deinen User zur docker-Gruppe hinzufügen (kein sudo vor jedem Befehl)
sudo usermod -aG docker $USER
newgrp docker
Version prüfen:
docker --version
docker compose version
Beispiel-App per Docker Compose deployen
Erstelle ein Arbeitsverzeichnis und eine docker-compose.yml:
mkdir -p ~/apps/meine-app && cd ~/apps/meine-app
nano docker-compose.yml
services:
web:
image: nginx:alpine
restart: unless-stopped
ports:
- "3000:80"
volumes:
- ./html:/usr/share/nginx/html:ro
environment:
- NGINX_HOST=localhost
- NGINX_PORT=80
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: sicheres_passwort_hier
POSTGRES_DB: appdb
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
# Statische Testseite erstellen
mkdir html
echo "<h1>App läuft!</h1>" > html/index.html
# Stack starten
docker compose up -d
# Status prüfen
docker compose ps
docker compose logs -f web
Der Nginx-Container läuft jetzt auf Port 3000, den wir oben im Nginx-Reverse-Proxy per proxy_pass weiterleiten. HTTP-Anfragen an meine-app.example.com landen damit direkt im Container.
HTTPS mit Let's Encrypt und Certbot
Certbot installieren
Let's Encrypt stellt kostenlose TLS-Zertifikate aus. Certbot automatisiert Ausstellung und Erneuerung vollständig:
sudo apt install -y certbot python3-certbot-nginx
Zertifikat ausstellen
sudo certbot --nginx -d meine-app.example.com
Certbot passt deinen Nginx-Server-Block automatisch für HTTPS an und richtet eine Weiterleitung von HTTP → HTTPS ein. Du wirst nach einer E-Mail-Adresse gefragt (für Ablaufwarnungen) und musst den Nutzungsbedingungen zustimmen.
Automatische Erneuerung
Let's Encrypt-Zertifikate laufen nach 90 Tagen ab. Certbot legt bei der Installation automatisch einen systemd-Timer an:
# Timer-Status prüfen
sudo systemctl status certbot.timer
# Erneuerung manuell testen (ohne tatsächliche Ausstellung)
sudo certbot renew --dry-run
Funktioniert der Dry-Run ohne Fehler, ist die automatische Erneuerung korrekt eingerichtet.
Eigene Dienste als systemd-Service
Nicht jede Anwendung läuft in Docker. Node.js-Apps, Python-Skripte oder eigene Binaries verwaltest du am besten als systemd-Service — das stellt sicher, dass sie nach einem Reboot automatisch neu starten.
Unit-File erstellen
sudo nano /etc/systemd/system/meine-node-app.service
[Unit]
Description=Meine Node.js Anwendung
Documentation=https://example.com
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/apps/node-app
ExecStart=/usr/bin/node /home/deploy/apps/node-app/index.js
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=meine-node-app
# Sicherheits-Härtung
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/deploy/apps/node-app/data
[Install]
WantedBy=multi-user.target
Service aktivieren und starten
sudo systemctl daemon-reload
sudo systemctl enable meine-node-app
sudo systemctl start meine-node-app
sudo systemctl status meine-node-app
Service-Management auf einen Blick
| Befehl | Wirkung |
|---|---|
systemctl start <service> | Service starten |
systemctl stop <service> | Service stoppen |
systemctl restart <service> | Service neu starten |
systemctl reload <service> | Config neu laden (ohne Stop) |
systemctl enable <service> | Autostart beim Boot aktivieren |
systemctl disable <service> | Autostart deaktivieren |
systemctl status <service> | Status und letzte Log-Zeilen |
journalctl -u <service> -f | Live-Logs des Service |
journalctl -u <service> --since "1h ago" | Logs der letzten Stunde |
Monitoring-Grundlagen
Prozesse und Ressourcen
# Interaktiver Prozess-Monitor
htop
# Docker Container-Ressourcen live
docker stats
# Plattenplatz
df -h
# Speichernutzung (RAM)
free -h
# Top-Prozesse nach CPU
ps aux --sort=-%cpu | head -20
Logs im Blick behalten
# System-Log live
sudo journalctl -f
# Nginx-Fehler-Log
sudo tail -f /var/log/nginx/error.log
# Docker-App Logs
docker compose -f ~/apps/meine-app/docker-compose.yml logs -f --tail=100
Tipp: Für produktive Server lohnt sich ein einfaches Monitoring-Tool wie Netdata (selbst gehostet, minimaler Ressourcenverbrauch) oder externe Dienste wie Better Uptime für Erreichbarkeits-Checks.
Backup-Strategie
Dateien mit rsync sichern
rsync ist das Schweizer Taschenmesser für Backups unter Linux. Inkrementell, schnell, bewährt:
# Einmalig — lokales Backup auf externen Mount
rsync -avz --delete \
/home/deploy/apps/ \
/mnt/backup/apps/
# Auf Remote-Server sichern (SSH)
rsync -avz --delete \
-e "ssh -i ~/.ssh/backup_key -p 22" \
/home/deploy/apps/ \
backup-user@backup-server.example.com:/backups/meine-app/
Automatisierung per Cron
crontab -e
Folgende Zeile sichert täglich um 03:00 Uhr:
0 3 * * * rsync -avz --delete /home/deploy/apps/ /mnt/backup/apps/ >> /var/log/backup.log 2>&1
Docker-Volumes sichern
# Postgres-Datenbank-Dump aus laufendem Container
docker exec -t <container_name> \
pg_dumpall -c -U postgres > /tmp/db_backup_$(date +%F).sql
# Anschließend mit rsync wegsichern
rsync -avz /tmp/db_backup_*.sql backup-user@backup-server:/backups/db/
Wichtig: Teste deine Backups regelmäßig durch einen Restore-Versuch. Ein Backup, das du nie getestet hast, ist kein Backup.
Häufige Fehler
Nginx-Config-Fehler nicht geprüft
Immer sudo nginx -t vor systemctl reload nginx. Ohne diesen Check kann ein Tippfehler in der Config den gesamten Webserver zum Absturz bringen.
Port bereits belegt
Wenn docker compose up mit bind: address already in use scheitert, lauscht bereits ein Prozess auf dem Port. Diagnose: sudo ss -tlnp | grep :3000. Gegebenenfalls den bestehenden Prozess stoppen oder einen anderen Port verwenden.
Let's Encrypt Rate Limits
Let's Encrypt erlaubt nur 5 Zertifikate pro Domain und Woche. Teste mit --dry-run, bevor du echte Zertifikate ausstellst, um das Limit nicht unnötig zu verbrauchen.
Docker ohne sudo nach Gruppe
Nach usermod -aG docker $USER muss man sich neu einloggen (oder newgrp docker ausführen), damit die Gruppe aktiv wird. Viele vergessen das und wundern sich über permission denied.
systemd-Service startet nicht nach Reboot
systemctl enable ohne anschließendes daemon-reload registriert den Service nicht korrekt. Reihenfolge beachten: daemon-reload → enable → start.
rsync löscht unbeabsichtigt Dateien
Das Flag --delete entfernt Dateien im Ziel, die im Quellverzeichnis nicht mehr vorhanden sind. Das ist für Backups oft gewollt, kann aber überraschen. Beim ersten Einsatz lieber --dry-run voranstellen.
Certbot findet Domain nicht Wenn Let's Encrypt die Domain nicht validieren kann, ist oft Port 80 durch die Firewall blockiert. Certbot benötigt HTTP-Zugang während der Validierung — UFW muss Port 80 freigeben, auch wenn später nur HTTPS genutzt wird.
Nächste Schritte
Mit diesem dritten Teil hast du die Grundlagen eines produktionsreifen Linux-Servers vollständig erarbeitet: In Teil 1 hast du einen frischen Server aufgesetzt, Benutzer angelegt und die grundlegende Netzwerkkonfiguration vorgenommen. In Teil 2 haben wir den Server durch SSH-Härtung, Fail2ban und UFW abgesichert. Jetzt, in Teil 3, läuft eine echte Anwendung hinter Nginx mit HTTPS, automatischen Deployments per Docker Compose, prozesssicherer Dienstverwaltung via systemd und einem soliden Backup-Plan.
Dieser Stack ist kein Spielzeug — er ist die Basis, auf der Dutzende produktiver Dienste laufen. Natürlich gibt es immer mehr zu lernen: Kubernetes für größere Setups, Terraform für Infrastructure-as-Code, Prometheus und Grafana für ernsthaftes Monitoring. Aber mit dem Fundament aus dieser Reihe bist du bestens gerüstet.
Wenn du Unterstützung bei der Einrichtung deines Servers brauchst, einen individuellen Stack aufbauen möchtest oder Fragen zu Deployment-Strategien für dein Projekt hast — melde dich gerne. Ich helfe dir, die richtige Lösung für deine Anforderungen zu finden.
Verwandte Artikel
- DevOps· 2 gemeinsame TagsCoolify als Heroku-Replacement — Self-Hosted PaaS in der Praxis
- DevOps· 2 gemeinsame TagsDevOps-Pipeline mit Coolify, Next.js & GitHub Actions — Auto-Deploy ohne Vendor-Lockin
- DevOps· 2 gemeinsame TagsSelf-Hosted Supabase mit Docker — Setup, Backups & Production-Härtung
- KI / AI· 1 gemeinsame TagsOptimal mit KI arbeiten – Teil 3: Coden mit KI