Zum Inhalt springen

Mein Produktions-Server-Setup mit openSUSE Leap

Veröffentlicht am 20. Feb. 2026 | ca. 6 Min. Lesezeit |

Viele Entwickler deployen auf gemanagte Cloud-Dienste und haben nie die volle Kontrolle über ihre Infrastruktur. Ich betreibe meinen Produktionsserver selbst — ein Hetzner Dedicated Server mit openSUSE Leap. Hier teile ich das komplette Setup.

Hardware und Betriebssystem

Der Server steht im Hetzner-Rechenzentrum in Falkenstein:

  • CPU: Intel Core i7-6700 (4 Cores / 8 Threads, 3.4 GHz)
  • RAM: 64 GB DDR4
  • Storage: 2x 512 GB NVMe SSD im Software-RAID
  • OS: openSUSE Leap 15.6

Warum openSUSE? Stabile Enterprise-Basis (SUSE Linux Enterprise), lange Support-Zyklen, exzellenter Paketmanager (zypper), und eine saubere Trennung zwischen System- und Drittanbieter-Paketen.

nginx mit Brotli: Maximale Komprimierung

nginx ist als Reverse Proxy und Webserver konfiguriert — mit Brotli-Modul für bessere Komprimierung als gzip:

server {
    listen 443 ssl http2;
    server_name www.example.de;

    root /srv/www/vhosts/example.de/httpdocs/public;

    # Dual compression: Brotli bevorzugt, gzip als Fallback
    gzip on;
    gzip_vary on;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript;

    brotli on;
    brotli_static on;    # Serviert pre-komprimierte .br Dateien
    brotli_comp_level 6;
    brotli_types text/plain text/css application/json application/javascript;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php-fpm/www.sock;
    }
}

brotli_static on ist der Schlüssel: Unsere Build-Pipeline erzeugt bereits .br-Dateien neben den Originalen. nginx serviert diese direkt, ohne zur Laufzeit komprimieren zu müssen. Brotli liefert 20-30% kleinere Dateien als gzip bei vergleichbarer CPU-Last. Implementiert habe ich Brotli in erster Linie aus SEO-Gründen: Neben gzip auch Brotli-Komprimierung anzubieten verbessert die Ladezeiten und wird von Suchmaschinen wie Google als positives Signal für das Ranking gewertet.

PHP-FPM via Unix Socket

PHP-FPM kommuniziert über einen Unix Socket statt TCP — das ist schneller, weil der gesamte TCP/IP-Stack umgangen wird:

upstream php-handler {
    server unix:/run/php-fpm/www.sock;
}

Der PHP-FPM-Pool läuft unter dem nginx-User, damit es keine Dateirechte-Konflikte gibt.

Security Headers

Jede Response enthält Security-Headers nach aktuellen Best Practices:

add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "0" always;  # XSS-Auditor wurde aus allen Browsern entfernt, CSP ist der korrekte Schutz
fastcgi_hide_header X-Powered-By;

Catch-All: Kein Fingerprinting

Requests an unbekannte Hostnamen werden sofort verworfen — ohne Antwort, ohne Zertifikats-Preisgabe:

# HTTP: Verbindung sofort schließen
server {
    listen 80 default_server;
    server_name _;
    return 444;
}

# HTTPS: TLS-Handshake abbrechen
server {
    listen 443 ssl default_server;
    server_name _;
    ssl_reject_handshake on;
}

Das verhindert, dass Port-Scanner die gehosteten Domains ermitteln können.

Mehrschichtige Sicherheitsarchitektur

SSH-Tarpit mit endlessh-go

Port 22 ist absichtlich offen — aber dahinter läuft kein SSH-Server, sondern endlessh-go. Dieses Tool hält SSH-Scanner endlos in einer Fake-Session gefangen, ohne echte Ressourcen zu verbrauchen. Der echte SSH-Server läuft auf einem anderen Port mit Key-Only-Authentifizierung.

CrowdSec als fail2ban-Nachfolger

CrowdSec analysiert Logs und blockiert Angreifer — ähnlich wie fail2ban, aber mit Community-Intelligence: Wenn ein Angreifer bei einem anderen CrowdSec-Nutzer auffällt, wird seine IP automatisch auch bei mir geblockt.

# CrowdSec installieren
sudo zypper install crowdsec crowdsec-firewall-bouncer-nftables

# Status prüfen
sudo cscli metrics
sudo cscli alerts list

Firewall mit nftables

firewalld mit nftables-Backend als Whitelist-Ansatz: Nur explizit erlaubte Ports sind von außen erreichbar (HTTP, HTTPS, Mail). Interne Dienste wie RabbitMQ sind nur auf localhost gebunden.

Let's Encrypt Wildcard-Zertifikate

Ein einziges Wildcard-Zertifikat für alle Domains — über DNS-Challenge, nicht HTTP-Challenge:

# certbot mit DNS-Plugin für INWX
certbot certonly \
    --dns-inwx \
    --dns-inwx-credentials /root/.inwx-credentials \
    -d "*.wunner-software.de" \
    -d "wunner-software.de"

Der Vorteil: Kein Port-80-Zugriff nötig, funktioniert auch für interne Dienste. Ein Deploy-Hook verteilt das erneuerte Zertifikat automatisch an nginx, Mailcow und RabbitMQ.

Automatisierte Backups

Ein PHP-Script mit pcntl_fork() für parallele Ausführung sichert täglich um 04:00 Uhr:

  1. Alte Backups löschen
  2. Parallel: nginx-Config, PHP-Config, Repositories, vHosts, Datenbanken
  3. Mailcow-Backup
  4. Nextcloud in Maintenance-Mode → tar + mysqldump → Maintenance-Mode aus
  5. rsync zur Hetzner Storage Box (nur wenn alle Tasks erfolgreich)
# Dry-Run zum Testen
php /root/scripts/backup-all.php --dry-run

Docker für isolierte Dienste

Nicht alles läuft nativ. Einige Dienste laufen isoliert in Docker:

  • Mailcow: Kompletter Mailserver (Postfix, Dovecot, SOGo, rspamd)
  • Umami: Datenschutzfreundliche Web-Analytics
  • Remark42: Selbst gehostetes Blog-Kommentarsystem
  • endlessh-go: SSH-Tarpit

Diese Dienste sind über nginx als Reverse Proxy erreichbar, gebunden an 127.0.0.1.

Sauberes Start/Stop mit systemd

Die Container-eigene Restart-Policy (unless-stopped) fängt Crashes ab, aber beim Server-Shutdown werden Container einfach gekillt — kein docker compose down, keine saubere Reihenfolge. Deshalb hat jedes Docker-Compose-Projekt einen eigenen systemd-Service:

[Unit]
Description=Mailcow Docker Compose
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/root/mailcow-dockerized
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=300
TimeoutStopSec=120

[Install]
WantedBy=multi-user.target

Type=oneshot mit RemainAfterExit=yes ist der Schlüssel: systemd betrachtet den Service als aktiv, auch nachdem docker compose up -d sich beendet hat. Beim Shutdown ruft systemd dann ExecStop auf — und die Container werden sauber heruntergefahren.

Über After= und Requires= lässt sich die Startreihenfolge steuern. Mailcow startet zuerst, da die anderen Projekte auf dessen Docker-Netzwerk oder Dienste angewiesen sein können:

Start: docker → Mailcow → endlessh + Umami + Remark42 (parallel) Stop: endlessh + Umami + Remark42 (parallel) → Mailcow → docker

Monitoring mit Grafana Cloud

Grafana Alloy (ehemals Grafana Agent) sammelt Metriken und Logs und sendet sie an Grafana Cloud Free. Damit habe ich Dashboards für CPU, RAM, Disk, nginx-Requests und CrowdSec-Alerts — ohne einen eigenen Grafana-Server betreiben zu müssen.

Fazit

Ein selbst verwalteter Server bedeutet mehr Arbeit als ein PaaS — aber auch volle Kontrolle, bessere Performance und niedrigere Kosten. Meine Motivation für dieses Setup war einfach: Ich kenne die Konsolenbefehle bereits und spare mir damit die Kosten für Tools wie Parallels & Co. Die initiale Einrichtung dauert, doch mit systemd, automatisierten Backups und CrowdSec läuft das System danach weitgehend wartungsfrei. Und ganz ehrlich — Linux ist heutzutage nicht mehr schwer zu bedienen, die Zeiten kryptischer Konfigurationen sind längst vorbei. Mit openSUSE Leap hat man zudem eine Distribution, die binärkompatibel mit dem Enterprise-Derivat SUSE Linux Enterprise ist, was langfristige Stabilität und professionellen Support garantiert. Und das Wissen, das man dabei aufbaut, ist in jedem DevOps-Kontext wertvoll.

Thomas Wunner

Thomas Wunner

Fachinformatiker für Anwendungsentwicklung mit Ausbildereignungsprüfung und über 14 Jahre Erfahrung im Aufbau skalierbarer Webanwendungen mit Symfony und Shopware. Abseits der Tastatur ist Thomas als Rettungsschwimmer in der Wasserwacht aktiv, legt als DJ auf und erkundet die Umgebung auf dem Motorrad.

Kommentare

Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.