Auf einem nginx-Server laufen praktisch nie nur eine, sondern mehrere Sites parallel — Hauptdomain, Subdomains, ein paar Tools, vielleicht eine Staging-Variante. Damit jeder Request beim richtigen Vhost landet, ist die genaue Funktion von listen und server_name zentral. Dieser Artikel geht in die Tiefe der Server-Blöcke: alle Flags der listen-Direktive, die exakten Match-Regeln für server_name (Wildcards, Regex, Spezial-Namen), die etablierten Patterns für HTTP-zu-HTTPS-Redirect und www-Kanonisierung, und liefert drei vollständige, praxisnahe Beispiel-Vhosts.
Was ein Server-Block ist und warum mehrere Sites ein einem Server
Ein Server-Block (server { ... }) ist die nginx-Repräsentation eines einzelnen virtuellen Hosts. Innerhalb der nginx-Hauptkonfiguration können beliebig viele Server-Blöcke existieren — jeder davon entspricht einer Site, einer Domain, einem Dienst. Mehrere Blöcke teilen sich die gleichen physischen Listen-Ports (80, 443) und werden über den Host-Header des HTTP-Requests auseinandergehalten.
Das nennt sich name-based virtual hosting: Der Client schickt im Request einen Host: mydomain.com-Header, nginx vergleicht den mit der server_name-Direktive jedes Blocks und wählt den passenden aus. Die Alternative — IP-based virtual hosting — gibt jeder Site eine eigene IP-Adresse und wählt anhand der Ziel-IP. In Zeiten knapper IPv4-Adressen ist letzteres unüblich; name-based ist der Standard.
Praktische Konsequenz: Auf einem einzigen 4-€-VPS lassen sich dutzende kleine Sites betreiben — alle teilen sich CPU, RAM und IP, jede bekommt aber ihre eigene Domain mit eigenem Konfigurations-Block.
Die listen-Direktive im Detail
listen legt fest, wo ein Server-Block erreichbar ist — Adresse, Port und einige zusätzliche Flags. Vollständige Form:
listen [<address>:]<port> [default_server] [ssl] [http2] [http3] [proxy_protocol] [reuseport] [...];Die wichtigsten Varianten in der Praxis:
| Direktive | Bedeutung |
|---|---|
listen 80; | Port 80 auf allen IPv4-Interfaces |
listen [::]:80; | Port 80 auf allen IPv6-Interfaces (notwendig für IPv6-Erreichbarkeit) |
listen 192.0.2.10:80; | Nur auf einer bestimmten IP — bei Multi-IP-Setups |
listen 127.0.0.1:8080; | Nur Loopback — typisch für Backends hinter Reverse-Proxy |
listen 80 default_server; | Markiert diesen Block als Default für Port 80 |
listen 443 ssl; | Port 443 mit TLS aktiv — Zertifikat im Block nötig |
listen 443 ssl default_server; | Default-Server für 443 |
Wichtig zu verstehen: Pro Listen-Port kann nur ein Server-Block das default_server-Flag tragen. Welcher das ist, entscheidet die Reihenfolge des Konfig-Ladens — wenn du default_server nicht explizit setzt, nimmt nginx den ersten Block, der den Port öffnet, automatisch als Default.
Die kombinierte Listen-Zeile ist auf modernen nginx-Versionen die Norm:
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
http2 on;Beachte: Auf nginx-Versionen vor 1.25.1 wurde http2 als Listen-Flag geschrieben (listen 443 ssl http2;). Seit 1.25.1 ist das eine eigene Direktive auf Server-Block-Ebene (http2 on;), die alte Form ist deprecated, aber noch unterstützt. Bei Distro-Paketen vor Ubuntu 24.04 / Debian 13 / EL 9 ist die alte Form noch gültig — beide tun dasselbe.
reuseportfür hochlast-Server: Mitlisten 80 reuseport;öffnet jeder Worker-Prozess seinen eigenen Listen-Socket statt eines geteilten. Das verbessert die Skalierung auf vielen Cores deutlich, kostet aber etwas RAM. Für Server mit weniger als ~5000 Requests/Sekunde unnötig.
server_name im Detail
server_name definiert, welche Hostnamen dieser Server-Block bedient. nginx vergleicht den Host-Header des eingehenden Requests mit dem Wert dieser Direktive. Vier Match-Varianten — laut offizieller nginx-Doku in dieser exakten Priorität:
| Priorität | Form | Beispiel | Match |
|---|---|---|---|
| 1 | Exakter Name | mydomain.com | nur exakt diese Domain |
| 2 | Wildcard mit führendem * | *.mydomain.com | alle Subdomains von mydomain.com |
| 3 | Wildcard mit folgendem * | mail.* | mail.com, mail.org, mail.de ... |
| 4 | Regulärer Ausdruck | `~^(www.)?mydomain.(com | de)$` |
nginx prüft in dieser Reihenfolge: erst exakte Treffer, dann Wildcards in beide Richtungen, zuletzt Regex. Beim ersten Treffer stoppt die Suche.
Mehrere Namen in einem Block werden mit Leerzeichen getrennt:
server_name mydomain.com www.mydomain.com api.mydomain.com;Wildcards mit führendem * sind die häufigste Variante:
server_name *.mydomain.com;
# matcht: www.mydomain.com, api.mydomain.com, foo.bar.mydomain.com
# matcht NICHT: mydomain.com (ohne Subdomain)Wenn die Hauptdomain mit abgedeckt sein soll, gibt es eine Kurzform mit führendem Punkt:
server_name .mydomain.com;
# matcht: mydomain.com UND www.mydomain.com UND alle weiteren SubdomainsRegex beginnt mit ~ und sollte mit ^ und $ verankert sein:
server_name ~^(www\.)?mydomain\.(com|de|org)$;Regex ist die langsamste Match-Form (sequentiell durchlaufen) und sollte nur eingesetzt werden, wenn Wildcards nicht reichen — z. B. bei mehreren TLDs oder dynamischen Mustern wie ~^kunde\d+\.mydomain\.com$.
Spezial-Namen, die häufig vorkommen:
| Wert | Bedeutung |
|---|---|
"" | Matcht Requests ohne Host-Header (selten, aber möglich bei alten Bots) |
_ | Hat keine spezielle Bedeutung — ist nur ein ungültiger Domain-Name. Konventionell als Platzhalter in default_server-Blöcken verwendet, weil er garantiert nichts trifft |
$hostname | Setzt den lokalen Hostname des Servers ein |
Wichtige Klarstellung zu
_: Der Underscore ist kein Catch-all-Mechanismus. Er matcht nichts. Was Server-Blöcke zu Catch-alls macht, ist ausschließlich dasdefault_server-Flag in derlisten-Direktive.server_name _;ist Konvention dafür, dass derserver_namebewusst nichts matchen soll, weil das Routing überdefault_serverläuft.
Beispiel: zwei Vhosts auf einem Server
Ein Mini-Setup mit zwei Sites — Hauptseite und Blog auf einer Subdomain:
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;
}
}server {
listen 80;
listen [::]:80;
server_name blog.mydomain.com;
root /var/www/blog;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}Beide aktivieren und testen:
sudo ln -s /etc/nginx/sites-available/mydomain.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/blog.mydomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxWenn DNS für beide Domains auf den Server zeigt, lieferst du jetzt zwei verschiedene Sites aus einer einzigen IP-Adresse aus. Test ohne DNS möglich:
curl -H "Host: mydomain.com" http://203.0.113.42/
curl -H "Host: blog.mydomain.com" http://203.0.113.42/Pattern: HTTP zu HTTPS umleiten
Sobald TLS eingerichtet ist (siehe nächste Artikel zu Let's Encrypt), gehört es zum guten Ton, Port-80-Requests sofort auf HTTPS umzulenken — sonst wird die Site potentiell unverschlüsselt angesprochen, was Mitlesen erlaubt und für Suchmaschinen ein Negativsignal ist.
Saubere Lösung mit zwei separaten Server-Blöcken:
# HTTP → HTTPS Umleitung
server {
listen 80;
listen [::]:80;
server_name mydomain.com www.mydomain.com;
# ACME-Challenge für Let's-Encrypt-Renewals weiterhin per HTTP zulassen
location /.well-known/acme-challenge/ {
root /var/www/html;
}
# Alles andere auf HTTPS
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS — die eigentliche Site
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name mydomain.com www.mydomain.com;
ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;
root /var/www/mydomain.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}Erläuterungen:
return 301ist ein permanenter Redirect — Browser und Suchmaschinen merken sich die Umleitung dauerhaft, bei späteren Requests gehen sie direkt auf HTTPS.$host$request_urirekonstruiert den Original-URI:$hostist die angefragte Domain (aus dem Host-Header),$request_uriist der angeforderte Pfad inklusive Query-String./.well-known/acme-challenge/wird vor der Catch-all-Umleitung erlaubt. Das ist nötig, damit Let's-Encrypt-Renewals weiterhin per HTTP funktionieren — der ACME-Authenticator legt eine Challenge in dieses Verzeichnis und holt sie über Port 80 ab, bevor das neue Zertifikat ausgestellt wird.returnist effizienter alsrewrite— das ist ein häufiger Fehler.rewrite ... permanent;funktioniert auch, ist aber komplexer und langsamer;return 301 ...ist die empfohlene Form.
Pattern: www zu non-www (oder umgekehrt) kanonisieren
SEO-Tools mahnen, dass https://mydomain.com und https://www.mydomain.com als zwei verschiedene Sites zählen, wenn beide ohne Redirect zu erreichen sind. Die saubere Lösung: eine Variante als kanonisch festlegen, die andere umleiten.
Variante A — non-www ist kanonisch, www wird umgeleitet:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name www.mydomain.com;
ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;
return 301 https://mydomain.com$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name mydomain.com;
ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;
root /var/www/mydomain.com;
# ... weitere Direktiven
}Variante B — www ist kanonisch: dieselbe Struktur, nur Roles vertauschen.
Welche Variante wählen? Es gibt keinen technischen Grund, eine zu bevorzugen — beide sind gleich valide. Für moderne, mobile-first-Sites ist die kürzere non-www-Form üblicher; für klassische Marken-Websites ist www verbreiteter. Wichtig ist nur die Konsistenz — eine Variante festlegen und überall (Sitemap, internal Links, Mail-Footer, Visitenkarte) die kanonische Form verwenden.
Wichtig: Das Zertifikat muss beide Domains abdecken. Bei Let's Encrypt läuft das über certbot ... -d mydomain.com -d www.mydomain.com — das Zertifikat enthält beide Subject Alternative Names.
Vollständiges Beispiel 1: Static-Site
Ein typischer Static-Site-Server (z. B. für eine mit Astro, Hugo, Jekyll generierte Website):
# HTTP → HTTPS
server {
listen 80;
listen [::]:80;
server_name mydomain.com www.mydomain.com;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://mydomain.com$request_uri;
}
}
# HTTPS für www → kanonisches non-www
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name www.mydomain.com;
ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;
return 301 https://mydomain.com$request_uri;
}
# HTTPS — die eigentliche Site
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name mydomain.com;
ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;
root /var/www/mydomain.com;
index index.html;
# Sicherheits-Header
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Static Assets aggressiv cachen
location ~* \.(jpg|jpeg|png|gif|webp|svg|css|js|woff2?)$ {
expires 1y;
access_log off;
add_header Cache-Control "public, immutable";
}
# Default-Routing
location / {
try_files $uri $uri/ $uri.html =404;
}
access_log /var/log/nginx/mydomain.com.access.log;
error_log /var/log/nginx/mydomain.com.error.log warn;
}Vollständiges Beispiel 2: Reverse-Proxy auf eine App
Eine Node.js-, Python- oder Go-App, die auf 127.0.0.1:3000 lauscht:
server {
listen 80;
listen [::]:80;
server_name app.mydomain.com;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name app.mydomain.com;
ssl_certificate /etc/letsencrypt/live/app.mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.mydomain.com/privkey.pem;
# Größere Uploads erlauben
client_max_body_size 25M;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
# Header an die Backend-App weitergeben
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;
# WebSocket-Support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts großzügig
proxy_read_timeout 300s;
proxy_connect_timeout 60s;
}
access_log /var/log/nginx/app.mydomain.com.access.log;
error_log /var/log/nginx/app.mydomain.com.error.log warn;
}Wichtige Punkte für Reverse-Proxy:
proxy_set_header X-Forwarded-Proto $scheme;ist wichtig, damit die Backend-App weiß, dass der Original-Request HTTPS war — sonst baut sie ggf. Redirects mithttp://auf, was Mixed-Content-Probleme erzeugt.Upgrade/Connection-Header sind für WebSocket-fähige Apps Pflicht (z. B. moderne Live-Chat-, Collaboration- oder Streaming-Apps). Bei reinen REST-APIs ohne WebSockets können sie weggelassen werden, schaden aber nicht.client_max_body_sizeim Default 1 MB — bei Datei-Uploads über das Limit hebt nginx die Verbindung mit413 Request Entity Too Largeab. Bewusst auf den Bedarf der App setzen.
Vollständiges Beispiel 3: PHP-FPM (z. B. WordPress)
Klassische LEMP-Stack-Konfiguration für eine PHP-Site:
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name cms.mydomain.com;
ssl_certificate /etc/letsencrypt/live/cms.mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cms.mydomain.com/privkey.pem;
root /var/www/cms.mydomain.com;
index index.php index.html;
client_max_body_size 64M;
# Default — try_files mit PHP als Fallback
location / {
try_files $uri $uri/ /index.php?$args;
}
# PHP-Dateien an PHP-FPM weiterreichen
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Static Assets aggressiv cachen
location ~* \.(jpg|jpeg|png|gif|webp|svg|css|js|woff2?)$ {
expires 30d;
access_log off;
add_header Cache-Control "public";
}
# Sensible Pfade explizit blockieren (typische CMS-Lecks)
location ~ /\.(?!well-known).* {
deny all;
}
access_log /var/log/nginx/cms.mydomain.com.access.log;
error_log /var/log/nginx/cms.mydomain.com.error.log warn;
}Erläuterungen zu den PHP-spezifischen Direktiven:
location ~ \.php$matcht alle URIs, die auf.phpenden — diese werden nicht direkt ausgeliefert, sondern an PHP-FPM weitergeleitet.fastcgi_pass unix:/run/php/php8.3-fpm.sock— Unix-Socket der PHP-FPM-Pool-Konfiguration. Auf Debian/Ubuntu ist der Pfadphp8.3-fpm.sock(Version anpassen!). Auf RHEL-Familie meistens/run/php-fpm/www.sock.include snippets/fastcgi-php.conf— Distro-Snippet, das die Standard-FastCGI-Parameter setzt. Wer diese Datei nicht hat, mussfastcgi_split_path_info,fastcgi_indexund ähnliches manuell pflegen.location ~ /\.(?!well-known).*— verhindert, dass Dotfiles (.git,.env,.htaccess) ausgeliefert werden. Die Negative-Lookahead-Klausel erlaubt/.well-known/weiter (für ACME-Challenges, Webfinger usw.).
Sicherheits-Checkliste
- Pro Vhost ein eigener Server-Block — keine Mehrfachnutzung mit
if-Konstrukten - Sowohl
listen 80;als auchlisten [::]:80;(und gleiches für 443) — IPv6 nicht vergessen - HTTP-zu-HTTPS-Umleitung sauber als separater Server-Block, nicht als
ifim HTTPS-Block - ACME-Challenge-Pfad vor der Catch-all-Umleitung freigegeben
default_serverexplizit definiert — nicht implizit dem ersten Block überlassen- Bei mehreren Domains pro Block: alle Namen im Zertifikat (Subject Alternative Names)
- Kanonische Variante (www oder non-www) festgelegt, andere wird umgeleitet
- Bei Reverse-Proxy:
X-Forwarded-Protoan Backend weitergeben, sonst Mixed-Content-Risiken client_max_body_sizean realistische Bedarfe angepasst (Default 1 MB ist oft zu wenig)
Häufige Fehler bei Server-Blöcken
if im server-Block für Redirect-Logik
Es gibt eine bekannte nginx-Konvention: „if is evil" innerhalb von server-Blöcken. if-Statements in nginx haben überraschende Semantik (sie öffnen einen impliziten Sub-Location-Block) und führen oft zu schwer debuggbaren Bugs. Für Redirects ist die saubere Lösung immer ein separater Server-Block mit return 301, nicht ein if ($host = www.mydomain.com)-Konstrukt.
Underscore _ als Catch-all missverstanden
server_name _; allein macht nichts. Nur die Kombination listen ... default_server; plus server_name _; macht den Block zum Catch-all. Wer nur _ setzt, ohne default_server zu nutzen, hat einen Block, der niemals matcht.
HTTPS-Block ohne IPv6-Listen
listen 443 ssl; ohne listen [::]:443 ssl; — IPv6-Verkehr fällt in den IPv6-Default-Server, was meistens nicht der gewünschte Vhost ist. Beide listen-Zeilen sind Pflicht auf Dual-Stack-Servern.
Zertifikat fehlt für www-Variante
Das Zertifikat wurde nur für mydomain.com ausgestellt, der www.mydomain.com-Vhost mit gleichem Zertifikat-Pfad lädt es zwar — Browser melden aber Zertifikatsfehler, weil der Hostname nicht im SAN-Feld steht. Bei Let's-Encrypt-Cert-Erstellung beide Domains angeben: certbot ... -d mydomain.com -d www.mydomain.com.
rewrite statt return für Redirects
rewrite ^/(.*)$ https://$host/$1 permanent; funktioniert, ist aber langsamer und semantisch anders als return 301 https://$host$request_uri;. Für reine Redirects immer return nutzen — rewrite ist für komplexere URI-Manipulationen reserviert.
proxy_pass ohne X-Forwarded-Proto
Das Backend weiß ohne diesen Header nicht, ob der Original-Request HTTPS war. Folge: in der App generierte Links und Redirects nutzen http:// statt https://, der Browser meldet Mixed-Content-Warnungen oder bricht den Request ab. proxy_set_header X-Forwarded-Proto $scheme; ist Pflicht bei jeder Reverse-Proxy-Konfiguration.
Vhost in sites-available, aber Symlink in sites-enabled vergessen
Auf Debian/Ubuntu ist die Datei in sites-available/ allein wirkungslos — sie muss in sites-enabled/ verlinkt werden, damit nginx sie lädt. Klassische Verwirrung: Datei wird editiert, nginx -t läuft sauber, aber die Site reagiert nicht. Lösung: ln -s ../sites-available/<name> /etc/nginx/sites-enabled/.
Verwandte Artikel
- nginx — Grundkonfiguration und Datei-Struktur — der vorhergehende Schritt, falls noch nicht eingerichtet
- Reverse-Proxy mit nginx — Vertiefung des Reverse-Proxy-Patterns aus Sektion 8
- SSL-Zertifikate mit Let's Encrypt und certbot — der HTTPS-Vhost braucht ein gültiges Zertifikat
- Server-Bootstrap: SSH-Hardening — Server-Sicherheit jenseits von nginx
Externe Quellen
- nginx HTTP Core Module Reference — alle Direktiven, die im
server-Block möglich sind - Server Names Documentation — exakte Match-Reihenfolge und Wildcard-Regeln
- How nginx processes a request — vollständige Beschreibung der Server- und Location-Auswahl
- Pitfalls and Common Mistakes — kuratierte Liste von typischen Fehlkonfigurationen, inklusive der berühmten „if is evil"-Diskussion