2004 hatte das Internet ein Problem: Apache ist für jede eingehende Verbindung einen neuen Thread oder Prozess geforkt. Bei 10.000 gleichzeitigen Verbindungen bedeutet das 10.000 Threads. Jeder Thread frisst RAM. Server kotzt ab. Website tot.
Igor Sysoev, ein russischer Entwickler, hat sich das angeschaut und gedacht: das ist strukturell kaputt. Seine Lösung: event-driven, asynchronous, non-blocking I/O. Ein Master-Prozess, ein paar Worker-Prozesse, kein Thread pro Connection. Fertig.
Apache: 1 Connection = 1 Thread = 1-10 MB RAM. Bei 1000 Usern gleichzeitig = 1-10 GB RAM allein für die Threads. Bevor ein einziges Byte deiner index.html ausgeliefert wurde.
nginx: 1 Worker-Prozess handelt tausende Connections über einen Event Loop. Warten auf Festplatte? Nächste Connection. Warten auf Netzwerk? Nächste Connection. Der Worker steht nie rum.
# So sieht nginx intern aus (vereinfacht)
master process # liest config, startet workers, bleibt am Leben
└── worker 0 # handelt ALLE connections auf CPU-Core 0
└── worker 1 # handelt ALLE connections auf CPU-Core 1
└── worker 2 # usw.
# Pro Worker: ein einziger Thread, ein Event Loop
# kein forken, kein spawnen, kein Speicher-Orgien
Apache-Config ist XML-artiger Brei aus den 90ern. nginx-Config ist so lesbar dass man sie beim ersten Mal fast versteht. Fast.
# Das hier ist eine vollständige, produktionsreife nginx-Config.
# Apache braucht für dasselbe 3x so viele Zeilen.
server {
listen 443 ssl;
server_name schwarz.cc www.schwarz.cc;
root /var/www/schwarz.cc;
index index.html;
# SSL via Let's Encrypt
ssl_certificate /etc/letsencrypt/live/schwarz.cc/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/schwarz.cc/privkey.pem;
# Gzip – weil du nicht im Jahr 2000 lebst
gzip on;
gzip_types text/html text/css application/javascript;
# Browser-Caching für statische Files
location ~* \.(css|js|png|jpg|ico)$ {
expires 30d;
add_header Cache-Control "public";
}
location / {
try_files $uri $uri/ =404;
}
}
# HTTP → HTTPS Redirect
server {
listen 80;
server_name schwarz.cc www.schwarz.cc;
return 301 https://$host$request_uri;
}
upstream-Block definieren, nginx verteilt automatisch. Round-Robin, least-connections, IP-hash – alles drin.limit_req_zone – drei Zeilen Config, DDoS gebremst. Kein Code.listen 443 ssl http2 schreiben. Multiplexing, Header-Compression, alles gratis. Apache hat dafür ein Modul das sich manchmal selbst hasst.sendfile on. Zero-copy. Die Datei geht vom Kernel-Buffer direkt ans Netzwerk-Interface ohne einmal in den Userspace zu wechseln.Das ist das Feature warum nginx auf jedem Produktions-Server läuft auf dem irgendwas Ernstes läuft. Das Prinzip: deine Applikation läuft intern auf irgendeinem Port, hört nur auf localhost, hat keinen direkten Kontakt mit dem bösen Internet. nginx sitzt davor, nimmt alles rein und entscheidet was durchkommt.
Klassisches Beispiel aus der Praxis: alter Odoo-Server. Odoo hat einen eingebauten Webserver der von aussen erreichbar ist – und der hatte lange eine nette Funktion: über eine bestimmte URL konnte man als Admin die komplette Datenbank löschen. Direkt. Im Browser. Ohne weitere Bestätigung. Danke, Odoo. nginx davor, Route blocken, Problem weg. Kein Odoo-Update nötig, kein Fummeln am Applikationscode.
https://deinodoo.ch/web/database/drop — öffentlich erreichbar, ein Button, Datenbank weg. Produktionsdaten. Weg. Für immer. Das ist kein hypothetisches Szenario, das ist ein bekanntes Problem das Leute tatsächlich getroffen hat. nginx hätte das in 3 Zeilen verhindert.
# Basis Reverse Proxy – App läuft auf Port 8069 (Odoo default)
server {
listen 443 ssl http2;
server_name odoo.firma.ch;
# SSL wie gehabt
ssl_certificate /etc/letsencrypt/live/odoo.firma.ch/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/odoo.firma.ch/privkey.pem;
# DIESE ROUTEN BLOCKIEREN – Datenbank-Manager von aussen sperren
location ~* ^/web/database {
deny all;
return 404; # 403 verrät dass die Route existiert. 404 ist stealthier.
}
# Alles andere an Odoo weitergeben
location / {
proxy_pass http://127.0.0.1:8069;
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;
}
}
Der wichtige Teil: proxy_pass http://127.0.0.1:8069 – die App hört nur auf localhost, ist also vom Internet direkt gar nicht erreichbar. Firewall-seitig ist Port 8069 zu. Nur nginx darf rein. Der Rest der Welt sieht nur Port 443.
# App nur auf localhost binden – Beispiel Odoo (/etc/odoo/odoo.conf)
xmlrpc_interface = 127.0.0.1
netrpc_interface = 127.0.0.1
# AWS Security Group: nur 80 + 443 offen lassen, App-Port zu
# Oder lokal via ufw:
sudo ufw allow 80,443/tcp
sudo ufw deny 8069
/web/database/drop aufrufen so oft er will. nginx gibt 404 zurück. Odoo bekommt davon nichts mit. Datenbank lebt. Chef ist glücklich.Das gleiche Prinzip funktioniert für jeden Applikationsserver: Node, Python/Django, Rails, Laravel, Java – egal. Die App kennt nur localhost, nginx ist der einzige Türsteher. Bonus: SSL, Gzip, Rate Limiting, Logging und Header-Sanitizing für jede App, ohne eine Zeile Applikationscode anzufassen.
.htaccess-Dateien die bei jedem Request von der Festplatte gelesen werden. Ein Prozess pro Connection. mod_php das direkt im Webserver-Prozess lebt wie ein Parasit. Apache ist das warum "meine Website ist langsam" zwanzig Jahre lang die Standardantwort im Hosting-Business war.
Du kannst nginx via apt aus den Ubuntu-Repos installieren – aber das ist oft eine veraltete Version die Ubuntu irgendwann eingefroren hat. Wer den aktuellen Stable-Build will, nimmt das offizielle nginx-Repo. Dauert 2 Minuten mehr, lohnt sich.
# 1. Abhängigkeiten
sudo apt install -y curl gnupg2 ca-certificates lsb-release ubuntu-keyring
# 2. Offiziellen nginx Signing Key importieren
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
# 3. Stable-Repo eintragen
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list
# 4. Offizielles Repo höher priorisieren als Ubuntu-Repo
echo "Package: *
Pin: origin nginx.org
Pin-Priority: 900" | sudo tee /etc/apt/preferences.d/99nginx
# 5. Installieren
sudo apt update && sudo apt install -y nginx
# Version prüfen – sollte aktueller Stable sein
nginx -v
# → nginx version: nginx/1.26.x
Der Unterschied: Ubuntu 24.04 liefert out-of-the-box nginx 1.24.x. Das offizielle Repo gibt dir 1.26.x Stable mit aktuellen Security-Fixes und HTTP/3-Support. Für einen Techblog der über nginx schreibt sollte man wenigstens die aktuelle Version laufen haben.
# Läuft sofort. Kein Neustart. Kein Wizard. Kein Bullshit.
sudo systemctl status nginx
# → active (running)
# Config testen (immer vor reload!)
sudo nginx -t
# → syntax is ok / test is successful
# Graceful reload (keine downtime)
sudo systemctl reload nginx