💡 Hinweis: Dieses Tutorial setzt voraus, dass du einen normalen Benutzer mit sudo-Rechten verwendest — wie im Tutorial Debian 13 Server absichern beschrieben.

Einleitung — Was ist Nginx und warum nicht Apache?

Nginx (ausgesprochen „Engine-X“) ist einer der beliebtesten Webserver weltweit — und das aus gutem Grund. Ursprünglich 2004 von Igor Sysoev entwickelt, um das C10K-Problem zu lösen (10.000 gleichzeitige Verbindungen), hat sich Nginx zum Standard für moderne Webinfrastruktur entwickelt.

Im Vergleich zu Apache bietet Nginx einige entscheidende Vorteile:

  • Event-basierte Architektur — Nginx nutzt ein asynchrones, nicht-blockierendes Modell. Apache erstellt standardmäßig einen Thread/Prozess pro Verbindung, was bei vielen gleichzeitigen Anfragen schnell Ressourcen frisst.
  • Geringerer Speicherverbrauch — Bei statischen Inhalten braucht Nginx einen Bruchteil des RAMs im Vergleich zu Apache.
  • Hervorragend als Reverse Proxy — Nginx ist die erste Wahl, wenn du Anwendungen wie Node.js, Python oder Docker-Container hinter einem Webserver betreiben willst.
  • Einfache Konfiguration — Die Konfig-Syntax ist klar, deklarativ und leicht lesbar.

Das heißt nicht, dass Apache schlecht ist — für bestimmte Use Cases (z.B. .htaccess-basierte Konfiguration) hat Apache seine Berechtigung. Aber für moderne Self-Hosting-Setups, APIs und statische Seiten ist Nginx die bessere Wahl.

In diesem Tutorial installieren wir Nginx auf Debian 13 (Trixie), verstehen die Verzeichnisstruktur, erstellen unsere erste Konfiguration und härten den Server mit Security Headers und Gzip-Kompression.

Voraussetzungen

Bevor wir starten, stelle sicher, dass Folgendes gegeben ist:

  • Debian 13 (Trixie) — frisch installiert oder bestehendes System. Falls noch nicht geschehen, folge Tutorial #1: Debian 13 Server Grundkonfiguration
  • Root-Zugang oder ein User mit sudo-Rechten
  • Port 80 muss von außen erreichbar sein (Firewall prüfen)
  • Eine Domain (optional, aber empfohlen für die Server-Block-Konfiguration)

Alle Befehle in diesem Tutorial werden mit sudo ausgeführt.

Nginx installieren

Nginx ist in den offiziellen Debian-Repositories enthalten. Die Installation ist denkbar einfach:

sudo apt update
sudo apt install nginx -y

Nach der Installation startet Nginx automatisch. Prüfe den Status:

sudo systemctl status nginx

Du solltest active (running) sehen. Öffne jetzt deinen Browser und navigiere zu http://deine-server-ip — die Nginx-Standardseite („Welcome to nginx!“) sollte erscheinen.

Falls du UFW als Firewall nutzt, gib den HTTP-Traffic frei:

sudo ufw allow 'Nginx HTTP'
sudo ufw status

Die Nginx-Verzeichnisstruktur verstehen

Bevor wir konfigurieren, solltest du wissen, wo was liegt. Nginx unter Debian organisiert sich so:

/etc/nginx/
├── nginx.conf              # Hauptkonfiguration
├── sites-available/        # Alle verfügbaren Server-Blöcke
│   └── default             # Standard-Konfiguration
├── sites-enabled/          # Aktive Server-Blöcke (Symlinks)
│   └── default → ../sites-available/default
├── conf.d/                 # Zusätzliche Konfig-Snippets
├── snippets/               # Wiederverwendbare Konfig-Teile
├── mime.types              # MIME-Type-Zuordnungen
└── modules-enabled/        # Geladene Module

Das wichtigste Prinzip: sites-available enthält alle Konfigurationen, sites-enabled nur die aktiven (als Symlinks). So kannst du Seiten aktivieren und deaktivieren, ohne Dateien zu löschen.

Erste Konfiguration — Ein Server-Block erstellen

Ein Server-Block (bei Apache „Virtual Host“ genannt) definiert, wie Nginx auf Anfragen für eine bestimmte Domain reagiert. Erstellen wir einen für example.com:

sudo nano /etc/nginx/sites-available/example.com

Füge folgende Konfiguration ein:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;
    root /var/www/example.com/html;
    index index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }
}

Erstelle das Webroot-Verzeichnis und eine Test-Seite:

sudo mkdir -p /var/www/example.com/html
echo '<h1>Willkommen auf example.com!</h1>' | sudo tee /var/www/example.com/html/index.html
sudo chown -R www-data:www-data /var/www/example.com

Aktiviere den Server-Block und deaktiviere die Standardseite:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default

Teste und lade die Konfiguration neu:

sudo nginx -t
sudo systemctl reload nginx

Wenn sudo nginx -t meldet syntax is ok und test is successful, ist alles korrekt.

Eine statische Website hosten

Nginx ist perfekt für statische Websites — HTML, CSS, JavaScript, Bilder. Lege deine Dateien einfach in das root-Verzeichnis deines Server-Blocks.

Ein typisches Setup für eine statische Seite:

/var/www/example.com/html/
├── index.html
├── about.html
├── css/
   └── style.css
├── js/
   └── app.js
└── images/
    └── logo.png

Für Single-Page Applications (SPAs) wie React oder Vue ändere die try_files-Direktive:

location / {
    try_files $uri $uri/ /index.html;
}

So werden alle Routen auf index.html umgeleitet — perfekt für Client-Side Routing.

Wichtige Nginx-Direktiven im Überblick

Hier die Direktiven, die du kennen solltest:

worker_processes

Definiert die Anzahl der Worker-Prozesse. Setze es auf auto, damit Nginx die Anzahl der CPU-Kerne automatisch erkennt:

# In /etc/nginx/nginx.conf
worker_processes auto;

server_name

Bestimmt, auf welche Domain(s) der Server-Block reagiert:

server_name example.com www.example.com;
server_name *.example.com;     # Wildcard
server_name _;                  # Catch-all (Default-Server)

location

Definiert, wie Nginx auf bestimmte URL-Pfade reagiert:

location / { }              # Alles
location /images/ { }       # Alles unter /images/
location ~ \.php$ { }       # Regex: PHP-Dateien
location = /favicon.ico { } # Exakter Match

root vs. alias

# root: Pfad + URI
location /static/ {
    root /var/www;  # Sucht in /var/www/static/
}

# alias: Ersetzt den Location-Pfad
location /static/ {
    alias /var/www/files/;  # Sucht in /var/www/files/
}

index

Welche Datei als Index geladen wird:

index index.html index.htm index.php;

Security Headers konfigurieren

Security Headers schützen deine Besucher vor gängigen Angriffen wie Clickjacking, XSS und MIME-Sniffing. Füge sie in deinen Server-Block oder in eine zentrale Snippet-Datei ein:

sudo nano /etc/nginx/snippets/security-headers.conf
# Verhindert Clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;

# Verhindert MIME-Type-Sniffing
add_header X-Content-Type-Options "nosniff" always;

# XSS-Schutz (für ältere Browser)
add_header X-XSS-Protection "1; mode=block" always;

# Referrer-Policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Permissions-Policy (ehemals Feature-Policy)
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

# Content-Security-Policy (anpassen!)
# add_header Content-Security-Policy "default-src 'self';" always;

Binde das Snippet in deinen Server-Block ein:

server {
    listen 80;
    server_name example.com;
    root /var/www/example.com/html;

    include snippets/security-headers.conf;

    location / {
        try_files $uri $uri/ =404;
    }
}

Tipp: Teste deine Headers mit securityheaders.com — Ziel ist mindestens ein A-Rating.

Gzip-Kompression aktivieren

Gzip reduziert die Größe von Textdateien (HTML, CSS, JS) um 60-80%. Das beschleunigt die Ladezeit massiv. Bearbeite die Hauptkonfiguration:

sudo nano /etc/nginx/nginx.conf

Füge im http-Block hinzu (oder entkommentiere vorhandene Zeilen):

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types
    text/plain
    text/css
    text/javascript
    application/javascript
    application/json
    application/xml
    application/xml+rss
    image/svg+xml
    font/woff2;

Erklärung:

  • gzip_comp_level 5 — Guter Kompromiss zwischen CPU-Last und Kompression (1-9 möglich)
  • gzip_min_length 256 — Dateien unter 256 Bytes werden nicht komprimiert (Overhead wäre größer als der Gewinn)
  • gzip_vary on — Setzt den Vary: Accept-Encoding Header für korrektes Caching

Logs verstehen — access.log und error.log

Nginx schreibt standardmäßig zwei Log-Dateien:

  • /var/log/nginx/access.log — Jeder Request wird hier protokolliert (IP, URL, Status-Code, User-Agent)
  • /var/log/nginx/error.log — Fehlermeldungen und Warnungen

Nützliche Befehle für die Log-Analyse:

# Live-Logs verfolgen
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log

# Die 10 häufigsten IP-Adressen
sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

# Alle 404-Fehler finden
sudo awk '$9 == 404' /var/log/nginx/access.log

# Requests pro Stunde zählen
sudo awk '{print $4}' /var/log/nginx/access.log | cut -d: -f1-2 | sort | uniq -c

Du kannst auch eigene Log-Formate pro Server-Block definieren:

access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;

Tipp: Logrotate ist auf Debian standardmäßig für Nginx konfiguriert (/etc/logrotate.d/nginx). Die Logs werden automatisch rotiert und komprimiert.

Konfiguration testen und Nginx neu laden

Die wichtigsten Befehle für den Alltag mit Nginx:

# Konfiguration auf Syntaxfehler prüfen (IMMER vor reload!)
sudo nginx -t

# Nginx neu laden (ohne Downtime — bevorzugt)
sudo systemctl reload nginx

# Nginx komplett neu starten (kurze Unterbrechung)
sudo systemctl restart nginx

# Nginx stoppen
sudo systemctl stop nginx

# Nginx starten
sudo systemctl start nginx

# Status prüfen
sudo systemctl status nginx

# Nginx beim Boot automatisch starten
sudo systemctl enable nginx

Goldene Regel: Führe sudo nginx -t immer vor reload oder restart aus. Ein Syntaxfehler in der Konfiguration würde Nginx sonst nicht starten und deine Seiten wären offline.

PHP mit Nginx (PHP-FPM)

Nginx kann von Haus aus kein PHP ausführen — anders als Apache mit mod_php. Stattdessen nutzt Nginx PHP-FPM (FastCGI Process Manager), einen eigenständigen PHP-Prozess, der Anfragen über das FastCGI-Protokoll entgegennimmt. Das hat handfeste Vorteile:

  • Bessere Performance — PHP-FPM läuft als eigener Daemon mit Worker-Pools, unabhängig von Nginx
  • Geringerer Speicherverbrauch — Nginx bedient statische Dateien direkt, nur PHP-Requests gehen an FPM
  • Bessere Skalierbarkeit — PHP-FPM Pools können pro Anwendung konfiguriert werden
  • Sicherheit — Nginx und PHP laufen als getrennte Prozesse mit eigenen Berechtigungen

PHP-FPM installieren

Debian 13 (Trixie) liefert PHP 8.4 in den offiziellen Repositories. Installiere PHP-FPM zusammen mit den wichtigsten Erweiterungen:

sudo apt install php-fpm php-mysql php-xml php-mbstring php-curl php-zip php-gd php-intl -y

Prüfe die installierte Version und den Status des FPM-Dienstes:

php -v
sudo systemctl status php8.4-fpm

Die Ausgabe sollte PHP 8.4.x zeigen und der Dienst als active (running) gemeldet werden. PHP-FPM erstellt automatisch einen Unix-Socket unter /run/php/php8.4-fpm.sock, über den Nginx die Anfragen weiterleitet.

Nginx für PHP konfigurieren

Damit Nginx PHP-Dateien an PHP-FPM weiterleitet, musst du einen location-Block in deiner Server-Konfiguration ergänzen. Öffne die Config deiner Seite:

sudo nano /etc/nginx/sites-available/example.com

Füge innerhalb des server { }-Blocks folgenden PHP-Location-Block hinzu:

server {
    listen 80;
    server_name example.com;
    root /var/www/example.com/html;
    index index.php index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }

    # PHP-Dateien an PHP-FPM weiterleiten
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;

        # Zusätzliche FastCGI-Parameter
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Zugriff auf .htaccess und versteckte Dateien blockieren
    location ~ /\.ht {
        deny all;
    }
}

Die wichtigsten Direktiven erklärt:

  • location ~ \.php$ — Fängt alle Anfragen an .php-Dateien ab (Regex-Match)
  • include snippets/fastcgi-php.conf — Lädt Debians Standard-FastCGI-Konfiguration (prüft ob die Datei existiert, setzt PATH_INFO etc.)
  • fastcgi_pass unix:/run/php/php8.4-fpm.sock — Leitet die Anfrage an den PHP-FPM Unix-Socket weiter
  • SCRIPT_FILENAME — Teilt PHP-FPM mit, welche Datei ausgeführt werden soll

Teste die Konfiguration und lade Nginx neu:

sudo nginx -t
sudo systemctl reload nginx

PHP testen mit phpinfo()

Erstelle eine Test-Datei, um zu prüfen, ob PHP korrekt funktioniert:

echo '<?php phpinfo(); ?>' | sudo tee /var/www/example.com/html/info.php

Rufe http://example.com/info.php im Browser auf. Du solltest die vollständige PHP-Informationsseite sehen — mit PHP-Version, geladenen Modulen und Konfiguration.

⚠️ Wichtig: Lösche die Datei nach dem Test sofort wieder! phpinfo() zeigt sensible Server-Informationen an, die ein Angreifer nutzen könnte.

sudo rm /var/www/example.com/html/info.php

PHP-FPM Pool-Konfiguration

PHP-FPM organisiert seine Worker-Prozesse in Pools. Der Standard-Pool heißt www und wird unter /etc/php/8.4/fpm/pool.d/www.conf konfiguriert:

sudo nano /etc/php/8.4/fpm/pool.d/www.conf

Die wichtigsten Einstellungen:

[www]
; Benutzer und Gruppe, unter der PHP-FPM läuft
user = www-data
group = www-data

; Socket-Pfad (muss zum Nginx fastcgi_pass passen!)
listen = /run/php/php8.4-fpm.sock

; Process Manager Modus:
; - static: Feste Anzahl Worker (gut für dedizierte Server)
; - dynamic: Skaliert zwischen min/max (Standard, empfohlen)
; - ondemand: Startet Worker nur bei Bedarf (gut für wenig Traffic)
pm = dynamic

; Maximale Anzahl gleichzeitiger Worker-Prozesse
pm.max_children = 10

; Worker die beim Start erstellt werden
pm.start_servers = 3

; Minimum an idle Workern
pm.min_spare_servers = 2

; Maximum an idle Workern
pm.max_spare_servers = 5

; Nach X Requests wird ein Worker neu gestartet (gegen Memory Leaks)
pm.max_requests = 500

Für einen kleinen VPS mit 1–2 GB RAM sind die Standardwerte meist ausreichend. Bei mehr Traffic oder größeren Anwendungen (WordPress, Laravel) solltest du pm.max_children an den verfügbaren RAM anpassen. Faustregel: Verfügbarer RAM / durchschnittlicher PHP-Prozess-Speicher (typisch 30–50 MB pro Prozess).

Nach Änderungen PHP-FPM neu starten:

sudo systemctl restart php8.4-fpm

Wichtige php.ini-Einstellungen

Die PHP-Konfiguration für FPM findest du unter /etc/php/8.4/fpm/php.ini. Einige Einstellungen solltest du je nach Anwendungsfall anpassen:

sudo nano /etc/php/8.4/fpm/php.ini

Wichtige Werte:

; Maximale Upload-Größe (Standard: 2M — für WordPress/CMS deutlich erhöhen)
upload_max_filesize = 64M

; Maximale POST-Größe (muss >= upload_max_filesize sein)
post_max_size = 64M

; Speicherlimit pro PHP-Prozess
memory_limit = 256M

; Maximale Ausführungszeit in Sekunden (für lange Imports/Backups erhöhen)
max_execution_time = 300

; Maximale Input-Zeit
max_input_time = 300

; Zeitzone setzen
date.timezone = Europe/Berlin

Nach Änderungen an der php.ini muss PHP-FPM neu gestartet werden:

sudo systemctl restart php8.4-fpm

Sicherheit: cgi.fix_pathinfo

Eine wichtige Sicherheitseinstellung betrifft cgi.fix_pathinfo. Standardmäßig steht dieser Wert auf 1, was dazu führen kann, dass PHP-FPM auch Dateien ausführt, die keine PHP-Dateien sind — ein potenzielles Sicherheitsrisiko.

Öffne die php.ini und setze:

; Sicherheit: Verhindert, dass PHP beliebige Dateien als PHP interpretiert
; MUSS auf 0 stehen, wenn Nginx mit try_files arbeitet
cgi.fix_pathinfo = 0

In Kombination mit dem snippets/fastcgi-php.conf von Debian (das try_files nutzt) ist dein Setup damit gegen Path-Traversal-Angriffe auf PHP geschützt.

sudo systemctl restart php8.4-fpm

Troubleshooting — Häufige Probleme und Lösungen

Port 80 ist bereits belegt

sudo ss -tlnp | grep :80

Falls Apache oder ein anderer Dienst Port 80 nutzt, stoppe ihn oder ändere den Port.

403 Forbidden

Häufige Ursachen:

  • Falsche Dateiberechtigungen: sudo chown -R www-data:www-data /var/www/example.com
  • Kein index.html vorhanden und autoindex ist deaktiviert
  • SELinux oder AppArmor blockiert den Zugriff

502 Bad Gateway

Tritt auf, wenn Nginx als Reverse Proxy konfiguriert ist, aber das Backend nicht erreichbar ist. Prüfe:

  • Läuft der Backend-Dienst? (sudo systemctl status dein-dienst)
  • Stimmt der proxy_pass-Port?
  • Prüfe /var/log/nginx/error.log für Details

Konfigurationsänderungen greifen nicht

  • Hast du sudo nginx -t und sudo systemctl reload nginx ausgeführt?
  • Ist der Server-Block in sites-enabled/ verlinkt?
  • Browser-Cache leeren (oder mit curl -I testen)

nginx -t zeigt Fehler

Die Fehlermeldung von sudo nginx -t ist meist sehr hilfreich — sie zeigt die Datei und Zeilennummer. Häufige Fehler:

  • Fehlende Semikolons am Zeilenende
  • Geschweifte Klammern nicht geschlossen
  • Doppelte server_name-Einträge in verschiedenen Blöcken

Zusammenfassung & Checkliste

Du hast jetzt einen vollständig konfigurierten Nginx-Webserver auf Debian 13. Hier die Checkliste:

  • ✅ Nginx installiert und gestartet
  • ✅ Verzeichnisstruktur verstanden (sites-available → sites-enabled)
  • ✅ Eigenen Server-Block erstellt
  • ✅ Statische Website gehostet
  • ✅ Wichtige Direktiven kennengelernt
  • ✅ Security Headers konfiguriert
  • ✅ Gzip-Kompression aktiviert
  • ✅ Logs verstanden und analysiert
  • ✅ nginx -t als Gewohnheit etabliert

Wie geht es weiter?

Dein Webserver läuft — aber ohne HTTPS ist er noch nicht produktionsreif. Im nächsten Tutorial richten wir kostenlose SSL-Zertifikate mit Let’s Encrypt ein, damit deine Seiten verschlüsselt und vertrauenswürdig sind.

Außerdem werden wir Nginx später als Reverse Proxy nutzen, um Docker-Container, Node.js-Apps und andere Dienste sicher hinter Nginx zu betreiben.