nginx ist seit Jahren der meistgenutzte Webserver im Internet. Auch wenn der Einstieg über fertige Tutorials oft schnell zum Ziel führt, hilft ein solides Verständnis der Architektur, der Konfigurations-Hierarchie und des Datei-Layouts dabei, nicht ständig im Trial-and-Error-Modus zu landen. Dieser Artikel zeigt, wie nginx intern aufgebaut ist, wo welche Konfigurations-Datei liegt, wie die Blockdirektiven (events, http, server, location) zusammenspielen, wie nginx beim eingehenden Request den passenden Server- und Location-Block auswählt und wie man Konfigurationen sicher reloadet, ohne den laufenden Verkehr zu unterbrechen.

Was nginx ist und wie es intern arbeitet

nginx (gesprochen „engine-x") ist ein Webserver, Reverse Proxy, Load Balancer und HTTP-Cache in einem Paket. Anders als der ältere Apache, der pro Verbindung einen Prozess oder Thread startet, arbeitet nginx event-basiert: Ein einzelner Worker-Prozess kann tausende Verbindungen gleichzeitig betreuen, weil er nicht auf jede einzelne wartet, sondern asynchron reagiert, sobald Daten ankommen oder gesendet werden müssen. Das ist der Hauptgrund, warum nginx mit deutlich weniger RAM und CPU auskommt als die klassischen Pro-Verbindung-Modelle — und warum er sich für hohe Concurrency (viele gleichzeitige Verbindungen) so gut eignet.

Die Prozess-Architektur ist zweistufig:

  • Master-Prozess — startet beim System-Boot, liest die Konfiguration ein, öffnet die Listen-Sockets (Port 80, 443 …) und startet die Worker-Prozesse. Der Master beantwortet selbst keine Requests; er verwaltet nur die Worker und reagiert auf Signale (Reload, Stop, Logs öffnen).
  • Worker-Prozesse — laufen meistens als unprivilegierter User (Default nginx oder www-data). Sie übernehmen die eigentliche Request-Bearbeitung. Ihre Anzahl wird über worker_processes gesteuert (Default historisch 1, in Distro-Paketen meistens auf auto gesetzt, was die Anzahl der CPU-Kerne nutzt).

Im Betrieb sieht das so aus:

Bash
ps -ef | grep nginx

Erwartete Ausgabe:

Output
root      1234     1  0 14:23 ?        00:00:00 nginx: master process /usr/sbin/nginx
www-data  1235  1234  0 14:23 ?        00:00:00 nginx: worker process
www-data  1236  1234  0 14:23 ?        00:00:00 nginx: worker process
www-data  1237  1234  0 14:23 ?        00:00:00 nginx: worker process
www-data  1238  1234  0 14:23 ?        00:00:00 nginx: worker process

Ein Master-Prozess als root, mehrere Worker-Prozesse als unprivilegierter User. Die Anzahl der Worker entspricht hier der Anzahl der CPU-Kerne.

Warum braucht der Master root? Ports unter 1024 (also 80 und 443) dürfen unter Linux klassisch nur von root geöffnet werden. Der Master öffnet diese Ports beim Start, gibt sie an die Worker weiter und entzieht den Workern dann die root-Rechte. So bleibt der eigentliche Request-Handler auf einem unprivilegierten User — eine Schwachstelle in nginx kompromittiert dann nur den www-data-User, nicht root.

Installation

nginx gibt es als Distro-Paket (komfortabel, gut integriert) und als Vendor-Paket vom nginx-Projekt selbst (immer aktuelle Version, eigenes Repo). Für die meisten Setups reicht das Distro-Paket; wer immer die neueste Stable-Version oder das Mainline-Release möchte, fährt das Vendor-Repo.

Auf Debian/Ubuntu:

Bash Distro-Paket
sudo apt update
sudo apt install nginx -y
sudo systemctl enable --now nginx

Auf AlmaLinux/Rocky/RHEL:

Bash Distro-Paket
sudo dnf install nginx -y
sudo systemctl enable --now nginx

Erste Verifikation:

Bash
nginx -v
systemctl status nginx
curl -I http://localhost

curl -I http://localhost sollte eine HTTP/1.1 200 OK-Antwort liefern. Wenn ja, läuft nginx und liefert die Default-Seite aus.

nginx Mainline vs. Stable. nginx pflegt zwei Release-Linien parallel: Mainline (neue Features, schnellere Updates) und Stable (nur Bugfixes und Security-Patches in einem festen Major-Release). Distro-Pakete beziehen sich auf Stable und sind oft ein paar Versionen hinterher. Für produktive Server reicht Stable in praktisch allen Fällen — neue HTTP/3-Features oder ähnliches sind selten der entscheidende Faktor, Stabilität und lange Patch-Pflege überwiegen.

Datei-Layout — wo liegt was?

Hier weichen Distros voneinander ab — wer die Unterschiede kennt, findet sich auf jedem Server zurecht.

Debian und Ubuntu bringen ein durchstrukturiertes Layout mit:

PfadBedeutung
/etc/nginx/nginx.confHaupt-Konfigurationsdatei. Enthält globale Direktiven und includes
/etc/nginx/conf.d/Drop-in-Verzeichnis für zusätzliche Konfigurationen
/etc/nginx/sites-available/Vhost-Konfigurationen — Lager (alle definierten Sites)
/etc/nginx/sites-enabled/Symlinks ins sites-available/ — nur was hier verlinkt ist, lädt
/etc/nginx/snippets/Wiederverwendbare Konfigurations-Bausteine
/etc/nginx/mime.typesMIME-Typen-Definitionen
/var/log/nginx/access.logStandard-Access-Log
/var/log/nginx/error.logStandard-Error-Log
/var/www/html/Standard-Webroot der Default-Site
/run/nginx.pidPID-Datei des Master-Prozesses

Das Schema sites-available + sites-enabled ist eine Debian-Konvention, kein nginx-Feature: ein Vhost wird in sites-available/ als Datei abgelegt, mit ln -s in sites-enabled/ verlinkt, und in der nginx.conf lädt eine include /etc/nginx/sites-enabled/*;-Zeile alles aus diesem Verzeichnis. Vorteil: ein Vhost lässt sich mit einem einzigen unlink-Befehl deaktivieren, ohne die eigentliche Konfigurationsdatei anzufassen.

RHEL/AlmaLinux/Rocky und das offizielle nginx-Vendor-Paket verwenden ein einfacheres Layout:

PfadBedeutung
/etc/nginx/nginx.confHaupt-Konfiguration
/etc/nginx/conf.d/Drop-in-Verzeichnis — Vhosts werden direkt hier abgelegt
/etc/nginx/default.d/Snippets, die in den Default-Server eingebunden werden (RHEL-Familie)
/var/log/nginx/Logs
/usr/share/nginx/html/Standard-Webroot
/run/nginx.pidPID-Datei

Hier gibt es kein sites-available/sites-enabled. Vhosts werden direkt in conf.d/ als <name>.conf abgelegt. Deaktivieren heißt: Datei umbenennen (z. B. von .conf zu .conf.disabled) oder löschen.

Welches Layout bevorzugen? Auf Debian/Ubuntu das vorhandene Schema beibehalten — alle Tutorials und automatisierten Werkzeuge (z. B. certbot, Plesk-Integrationen) erwarten es. Auf RHEL-Familie das conf.d/-Schema nutzen. Wer ein einheitliches Layout über mehrere Distros hinweg braucht (z. B. mit Ansible), kann auf allen Systemen die sites-available/sites-enabled-Struktur manuell einrichten — aber das ist Geschmackssache.

Konfigurations-Struktur — Block-Direktiven

nginx-Konfiguration besteht aus zwei Arten von Direktiven:

  • Einfache Direktiven — Name, Parameter, Strichpunkt: worker_processes auto;
  • Block-Direktiven — Name, optionale Parameter, geschweifte Klammern mit weiteren Direktiven darin: events { ... }

Block-Direktiven sind Kontexte — sie definieren den Gültigkeitsbereich der enthaltenen Direktiven. Die Hierarchie ist fest:

Schema
main context (Datei-Top-Level)

├── events {  }
│       └── worker_connections, multi_accept, ...

└── http {  }

    ├── (HTTP-Defaults: gzip, types, log_format, sendfile, ...)

    └── server {  }              ← ein Vhost

        ├── (server-Defaults: listen, server_name, root, ...)

        └── location <pattern> {  }
            └── (Pfad-spezifische Regeln)

Eine Beispiel-Konfiguration, die alle vier Ebenen zeigt:

Nginx /etc/nginx/nginx.conf (Auszug)
# main context — gilt für den gesamten nginx-Prozess
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log warn;

events {
    # gilt für die Worker-Connection-Verarbeitung
    worker_connections 1024;
    multi_accept on;
}

http {
    # gilt für alle HTTP-Vhosts darunter
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    log_format    main '$remote_addr - $remote_user [$time_local] '
                       '"$request" $status $body_bytes_sent '
                       '"$http_referer" "$http_user_agent"';
    access_log    /var/log/nginx/access.log main;
    sendfile      on;
    keepalive_timeout 65;
    gzip on;

    # Drop-in-Verzeichnisse einlesen
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Die wichtigsten Eigenschaften der Hierarchie:

  • Direktiven werden vererbt. Was im http-Block steht, gilt für jeden server-Block darin, sofern er den Wert nicht selbst überschreibt. gzip on; im http-Block aktiviert gzip für alle Vhosts.
  • Spezifischere Ebenen überschreiben generischere. Ein gzip off; in einem server-Block deaktiviert gzip für genau diesen Vhost, ohne den globalen Default anzufassen.
  • Reihenfolge spielt selten eine Rolle für die Logik, aber die Reihenfolge der include-Statements bestimmt, in welcher Reihenfolge Vhosts geladen werden — relevant für den Default-Server-Mechanismus (siehe Sektion 8).

Erste eigene Vhost-Datei

Ein einfacher Webserver, der eine statische Website ausliefert:

Bash
sudo nano /etc/nginx/sites-available/mydomain.com
Nginx /etc/nginx/sites-available/mydomain.com
server {
    listen 80;
    listen [::]:80;
    server_name mydomain.com www.mydomain.com;

    root /var/www/mydomain.com;
    index index.html;

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

    access_log /var/log/nginx/mydomain.com.access.log;
    error_log  /var/log/nginx/mydomain.com.error.log warn;
}

Was die einzelnen Direktiven tun:

  • listen 80; und listen [::]:80; — der Server lauscht auf Port 80 für IPv4 und IPv6. Ohne die [::]:80-Variante wird IPv6 ignoriert.
  • server_name — die Domain(s), für die dieser Vhost zuständig ist. nginx wertet den Host-Header des eingehenden Requests aus und sucht den Vhost mit passendem server_name.
  • root — Wurzelverzeichnis, in dem die ausgelieferten Dateien liegen.
  • index — Default-Datei, wenn die URL auf ein Verzeichnis zeigt (/ → sucht index.html).
  • location / — Pfad-Regel; das / matcht alle URIs. try_files versucht, die angeforderte Datei zu finden, dann ein Verzeichnis, sonst Fehler 404.
  • access_log und error_log — separate Logs pro Vhost. Hilft bei Debugging und beim Trennen mehrerer Sites.

Webroot anlegen und Test-Datei einlegen:

Bash
sudo mkdir -p /var/www/mydomain.com
echo '<h1>Hallo Welt</h1>' | sudo tee /var/www/mydomain.com/index.html
sudo chown -R www-data:www-data /var/www/mydomain.com

Vhost in sites-enabled/ aktivieren (Debian/Ubuntu-Schema):

Bash
sudo ln -s /etc/nginx/sites-available/mydomain.com /etc/nginx/sites-enabled/

nginx -t und Reload — ohne Aussperrung

Bevor jede Konfigurations-Änderung in den laufenden nginx übernommen wird: immer testen.

Bash
sudo nginx -t

nginx -t parst die gesamte Konfiguration (inklusive aller include-Dateien) und meldet entweder Erfolg oder den genauen Fehler mit Datei und Zeilennummer:

Output
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Erst nach erfolgreichem Test wird reloadet — niemals mit restart, sofern ein Reload möglich ist:

Bash
sudo systemctl reload nginx

Was beim Reload tatsächlich passiert (laut offizieller nginx-Dokumentation):

  1. Der Master-Prozess validiert die neue Konfiguration syntaktisch.
  2. Ist alles ok, startet er neue Worker-Prozesse mit der neuen Konfiguration.
  3. Die alten Worker werden signalisiert, graceful zu beenden: keine neuen Verbindungen annehmen, bestehende Verbindungen zu Ende bedienen, dann beenden.

Das Resultat: kein einziger Request wird verloren, kein User merkt etwas vom Reload. Bei restart hingegen wird der gesamte nginx-Prozess heruntergefahren und neu gestartet — kurzzeitig keine Verbindungen möglich, bestehende Sessions werden hart abgebrochen.

Wichtig: reload funktioniert nur, wenn die neue Konfiguration valide ist. nginx -t vorher fängt Syntaxfehler ab. Ein zweiter, subtilerer Failure-Modus: die Konfiguration ist syntaktisch korrekt, aber die geöffneten Listen-Sockets passen nicht (z. B. ein neuer Vhost will Port 8080 öffnen, der schon belegt ist) — dann wirft auch reload einen Fehler. Im Zweifel die Logs danach ansehen: journalctl -u nginx -n 50 zeigt die letzten Service-Meldungen.

Alternative Signale (für Skript-Workflows):

Bash
sudo nginx -s reload    # entspricht systemctl reload nginx
sudo nginx -s reopen    # öffnet Log-Dateien neu (z. B. nach logrotate)
sudo nginx -s quit      # graceful stop (alte Verbindungen ausbedienen)
sudo nginx -s stop      # sofortiger stop

Wie nginx Server- und Location-Blöcke auswählt

Verstehen, wie nginx einen eingehenden Request einem server- und einem location-Block zuordnet, ist der zentrale Punkt fürs Debugging. Die Auswahl läuft in zwei Schritten:

Schritt 1 — Server-Auswahl:

  • nginx schaut sich die listen-Direktive jedes server-Blocks an und filtert die, die zur eingehenden IP-Adresse und Port passen.
  • Unter den verbleibenden vergleicht es den Host-Header des Requests mit dem server_name jedes server-Blocks.
  • Match-Reihenfolge:
    1. Exakter Treffer (server_name mydomain.com)
    2. Längster Wildcard-Treffer mit Asterisk am Anfang (*.mydomain.com)
    3. Längster Wildcard-Treffer mit Asterisk am Ende (mail.*)
    4. Erster Regex-Treffer (~^mail\.)
  • Findet nichts: der Default-Server für diesen Listen-Port wird genommen (siehe Sektion 8).

Schritt 2 — Location-Auswahl (innerhalb des gefundenen Servers):

Laut nginx-Doku:

  1. nginx prüft alle Prefix-Locations und merkt sich die mit dem längsten passenden Prefix.
  2. Dann werden Regex-Locations in Reihenfolge geprüft. Match → Regex gewinnt.
  3. Falls keine Regex matcht, gewinnt die längste Prefix-Location aus Schritt 1.
  4. Eine Prefix-Location mit = (exact match) gewinnt sofort, ohne Regex-Prüfung.
  5. Eine Prefix-Location mit ^~ blockiert die Regex-Prüfung — wenn sie matcht, ist sie der Treffer.

Beispiel mit allen Varianten:

Nginx
server {
    listen 80;
    server_name mydomain.com;

    # 1) Exakter Match — gewinnt sofort, keine weitere Prüfung
    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    # 2) Prefix-Match mit ^~ — blockiert Regex
    location ^~ /static/ {
        root /var/www/cache;
        expires 1y;
    }

    # 3) Regex-Match
    location ~* \.(jpg|jpeg|png|gif|webp|svg)$ {
        expires 30d;
        access_log off;
    }

    # 4) Prefix-Match (Default-Suche)
    location / {
        try_files $uri $uri/ =404;
    }
}

Test-Cases:

  • Request auf /favicon.ico → Regel 1 (exakter Match).
  • Request auf /static/css/main.css → Regel 2 (^~ blockiert Regex).
  • Request auf /uploads/foto.jpg → Regel 3 (Regex matcht, gewinnt gegen Regel 4).
  • Request auf /about/ → Regel 4 (kein anderer Match).

Faustregel: Wenn dir unklar ist, welcher Block greift, im error_log mit error_log /var/log/nginx/error.log debug; (debug-Level) testen — nginx loggt dann die getroffenen Locations. Nicht im Produktivbetrieb laufen lassen, das Log-Volumen ist enorm.

Default-Server und Catch-all

Der Default-Server ist der Vhost, der greift, wenn der Host-Header zu keinem server_name passt — typischerweise bei direkten IP-Aufrufen, Bot-Scans oder Requests an undefinierte Subdomains. Ohne expliziten Default-Server nimmt nginx den ersten für den jeweiligen Port definierten Server.

Das ist meistens nicht das Gewünschte: ein Bot, der gegen die Server-IP scannt, bekommt dann zufällig die Inhalte des „ersten" Vhosts ausgeliefert. Besser: einen expliziten Catch-all-Server definieren, der einen sauberen 444 (Connection Closed) zurückgibt:

Nginx /etc/nginx/sites-available/00-default
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    server_name _;

    # Selbst signiertes Zertifikat oder ein "Default-Cert"
    ssl_certificate     /etc/ssl/certs/ssl-cert-snakeoil.pem;
    ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

    return 444;
}

Erläuterungen:

  • default_server — Flag in der listen-Direktive, das diesen Server als Default für den Port markiert. Pro Listen-Port nur einer möglich.
  • server_name _;_ ist ein Konventions-Platzhalter für „matcht nichts spezifisches". Da default_server ohnehin alles auffängt, was sonst nirgendwo passt, ist der server_name hier irrelevant.
  • return 444; — nginx-spezifischer Statuscode, der die Verbindung sofort ohne Antwort schließt. Bots und Scanner bekommen dadurch keine Information.

Die Datei am Anfang einbinden — Konvention: 00-default als Dateiname, damit sie alphabetisch zuerst kommt und der default_server-Flag auf jeden Fall greift:

Bash
sudo ln -s /etc/nginx/sites-available/00-default /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Wichtig: bei listen 443 ssl default_server müssen Zertifikatsdateien existieren, sonst startet nginx nicht. Das snakeoil-Zertifikat aus dem ssl-cert-Paket ist dafür ein etablierter Platzhalter (auf Debian/Ubuntu vorinstalliert), oder man verweist auf eines der echten Site-Zertifikate.

worker_processes und worker_connections

Zwei Direktiven bestimmen, wie viele gleichzeitige Verbindungen ein nginx-Server theoretisch bedienen kann.

worker_processes — Anzahl der Worker-Prozesse. Default des nginx-Projekts ist 1, in Distro-Paketen ist meistens auto voreingestellt:

Nginx /etc/nginx/nginx.conf
worker_processes auto;

auto setzt die Anzahl auf die Anzahl der CPU-Kerne. Das ist auf praktisch allen Servern der richtige Wert — mehr Worker als Cores bringt nichts, weil sie sich um dieselben Cores streiten würden, weniger lässt CPU-Kapazität liegen.

worker_connections — maximale Anzahl gleichzeitiger Verbindungen pro Worker:

Nginx /etc/nginx/nginx.conf
events {
    worker_connections 1024;
}

nginx-Default ist 512. Distro-Pakete heben das auf 768 oder 1024 an. Wichtig zu wissen (laut nginx-Doku): dieser Wert umfasst alle Verbindungen, nicht nur Client-Verbindungen — also auch Verbindungen zu Upstream-Servern bei Reverse-Proxy-Setups, DNS-Resolver-Verbindungen etc.

Theoretisches Maximum an gleichzeitigen Client-Verbindungen:

Berechnung
max_clients = worker_processes × worker_connections / 2

Die Division durch 2 berücksichtigt Reverse-Proxy-Setups, in denen jede Client-Verbindung auch eine Upstream-Verbindung erfordert.

Beispielrechnung für einen 4-Core-VPS mit worker_processes auto; und worker_connections 1024;:

  • 4 Worker × 1024 Verbindungen = 4096 gleichzeitige Verbindungen
  • Bei Reverse-Proxy: ca. 2048 gleichzeitige Client-Verbindungen

Für die meisten Setups deutlich mehr, als jemals gleichzeitig gebraucht wird. Wer aber mehrere zehntausend gleichzeitige Verbindungen erwartet (Streaming, Echtzeit-Apps), hebt worker_connections auf 4096 oder höher an. Achtung: dann auch das System-Limit für Open File Descriptors prüfen (ulimit -n, in /etc/security/limits.conf), sonst trifft der Kernel zuerst die Wand.

Logs lesen

nginx schreibt zwei wichtige Logs:

  • access_log — jeder beantwortete Request, ein Eintrag pro Zeile. Format konfigurierbar via log_format.
  • error_log — Fehler und Warnungen. Standardmäßig im warn-Level, einstellbar von debug (sehr detailliert) bis emerg (nur kritisch).

Live mitlesen:

Bash
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log

Im Standard-Format (combined) sieht eine Access-Log-Zeile so aus:

Output
203.0.113.42 - - [08/May/2026:14:23:45 +0200] "GET /index.html HTTP/1.1"
200 615 "https://google.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"

Die Felder: Quell-IP, RemoteUser (meist -), Zeitstempel, Request-Zeile, Status-Code, Bytes gesendet, Referer, User-Agent.

Häufige Ad-hoc-Analysen:

Bash Top-IPs nach Request-Anzahl
sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
Bash Status-Code-Verteilung der letzten Stunde
sudo awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
Bash Letzte 50 Fehler
sudo tail -n 50 /var/log/nginx/error.log

Bei vielen Vhosts lohnt es sich, pro Vhost separate Logs zu führen (siehe Beispiel in Sektion 5) — sonst landen Logs aller Sites in derselben Datei und Analysen werden mühsam. Logrotation wird üblicherweise vom Distro-Paket eingerichtet (/etc/logrotate.d/nginx); standardmäßig täglich, gzip-komprimiert, 14 Tage Aufbewahrung.

Sicherheits- und Performance-Checkliste

  • nginx läuft mit eigenem Worker-User (www-data / nginx), nicht als root
  • worker_processes auto; und worker_connections zur Last passend
  • Pro Vhost: eigene access_log und error_log, einfacheres Debugging
  • Default-Server eingerichtet, der mit 444 schließt — keine Site wird per IP-Scan zufällig ausgeliefert
  • nginx -t ist Pflicht vor jedem reload — auch bei vermeintlich kleinen Änderungen
  • reload statt restart, außer der Service ist tatsächlich kaputt
  • server_tokens off; im http-Block, damit nginx nicht seine Versionsnummer in Headers verrät
  • Logs werden rotiert — nicht selbst rotieren, sondern auf logrotate-Standard verlassen
  • Mainline vs. Stable bewusst gewählt — auf produktiven Servern Stable

Häufige Fehler bei der nginx-Grundkonfiguration

Konfiguration direkt in nginx.conf gepflegt

Wenn alle Vhosts in der Hauptdatei stehen, wird sie schnell unübersichtlich, und Distro-Updates können Konflikte verursachen. Vhosts gehören in sites-available/ (Debian/Ubuntu) oder conf.d/ (RHEL), nicht in die nginx.conf. Die Hauptdatei sollte nur globale Defaults und include-Statements enthalten.

Reload ohne nginx -t

Ein Tippfehler in der Konfig sorgt beim reload dafür, dass nginx den Reload ablehnt — der laufende Prozess bleibt mit der alten Konfig aktiv, was im ersten Moment nicht auffällt. Erst beim nächsten Reboot oder Restart bricht der Service-Start dann komplett ab. nginx -t als Pflicht-Schritt verhindert das.

Fehlender default_server lässt zufällige Vhost antworten

Ohne expliziten default_server ist der erste in nginx geladene Vhost der Default — bei IP-Scans wird zufällig dessen Inhalt ausgeliefert. Klassiker: ein Admin-Subdomain wird per IP angesprochen, weil nginx den ersten geladenen Vhost wählt. Catch-all mit return 444; ist Standard.

server_name ohne IPv6-Listen

listen 80; öffnet nur IPv4. Auf Dual-Stack-Servern bleibt IPv6-Verkehr ungehört — Clients, die per IPv6 ankommen, bekommen keine Antwort. Immer auch listen [::]:80; (und das gleiche für 443) ergänzen.

root in location, nicht in server

root darf in beiden Kontexten stehen, aber Mehrfach-root führt schnell zu schwer nachvollziehbarem Verhalten. Saubere Konvention: root im server-Block, nur in location-Blöcken überschreiben, wenn ein bestimmter Pfad einen anderen Speicherort nutzen soll (z. B. ein Cache-Verzeichnis).

Logs werden nicht rotiert oder zu lange aufbewahrt

Auf einem trafficstarken Server wachsen Access-Logs schnell auf mehrere GB pro Tag. Ohne logrotate fluten sie die Disk. Distro-Pakete bringen das automatisch mit; bei selbst kompiliertem nginx oder bei ungewöhnlichen Pfaden (access_log in einem Ordner, der nicht von logrotate erfasst wird) muss das bewusst eingerichtet werden.

server_tokens an, also Versionsnummer verraten

Default ist server_tokens on; — nginx schickt seine Versionsnummer im Server-Header. Angreifer können damit gezielt nach bekannten Schwachstellen für genau diese Version suchen. server_tokens off; im http-Block schaltet das aus.

Verwandte Artikel

Externe Quellen

/ Weiter

Zurück zu Web-Server

Zur Übersicht