Files

255 lines
9.8 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: nginx-vhost-deployment
description: "Nginx-Webserver mit Name-based Virtual Hosting auf Ubuntu-Server installieren, konfigurieren und deployen – inkl. Fallstricke, DNS-Checks und Test-Workflow"
version: 1.0.0
author: Agent A (Deployer)
platforms: [linux, ubuntu]
---
# Nginx Virtual Host Deployment
Installation und Konfiguration von nginx mit mehreren Virtual Hosts (name-based) auf einem Ubuntu-VPS. Deckt den gesamten Workflow ab: DNS-Prüfung → Installation → VHost-Konfiguration → Berechtigungen → Firewall → Test → SSL-Vorbereitung.
## Auslöser (Trigger)
- „Webserver installieren"
- „Webseite online stellen"
- „Domain aufsetzen"
- „nginx einrichten"
- „Virtual Host konfigurieren"
- „Mehrere Domains auf einem Server"
---
## Schritt 1: DNS-Check VOR der Installation
**Immer zuerst prüfen**, ob die Domains auf den Server zeigen – sonst arbeitet man ins Leere.
```bash
for domain in domain1.de domain2.de; do
echo "=== DNS: $domain ==="
dig +short A "$domain"
dig +short AAAA "$domain"
done
```
**Interpretation:**
- Zeigt auf **richtige IP** → ✅ Weiter
- Zeigt auf **Cloudflare-IPs** (104.x, 172.x) → ⚠️ Nutzer muss Cloudflare-Proxy deaktivieren (orange→grau)
- Zeigt auf **fremde IP** → ❌ Nutzer muss A-Record ändern
- **Kein Eintrag** → ❌ Nutzer muss A-Record anlegen
**Server-IP herausfinden:** `curl -s ifconfig.me` oder aus Memory/Konfiguration.
---
## Schritt 2: nginx installieren
```bash
sudo apt-get update -qq
sudo apt-get install -y -qq nginx certbot python3-certbot-nginx
sudo systemctl enable --now nginx
```
**NICHT vergessen:** `certbot` und `python3-certbot-nginx` gleich mitinstallieren – spart später Arbeit.
---
## Schritt 3: Verzeichnisstruktur pro Domain
```bash
sudo mkdir -p /var/www/DOMAIN.de/html
sudo mkdir -p /var/www/DOMAIN.de/logs
sudo chown -R www-data:www-data /var/www/DOMAIN.de/
sudo chmod -R 755 /var/www/DOMAIN.de/
```
⚠️ **PITFALL – 403 Forbidden:** Wenn nginx 403 zurückgibt, sind die Dateien/Berechtigungen schuld:
- **Ursache:** Dateien gehören `root:root`, aber nginx-Worker läuft als `www-data`
- **Fix:** `sudo chown -R www-data:www-data /var/www/` + `sudo chmod -R 755 /var/www/`
- **Test:** `ps aux | grep nginx` → Worker-User prüfen
---
## Schritt 4: VHost-Konfiguration erstellen
### ⚠️ PITFALL – sites-available vs sites-enabled: SEPARATE Dateien, nicht nur Symlink
Auf manchen Setups sind `sites-available/DOMAIN` und `sites-enabled/DOMAIN` **zwei
getrennte Dateien** (kein Symlink). nginx lädt NUR `sites-enabled/`. Wer nur die
`available`-Datei editiert und reloadet, sieht **keine Änderung** — und `nginx -t`
meldet trotzdem OK (er testet die alte, aktive Datei → "grün" aber unverändert).
Symptom (2026-08-04 PersoEnt): neuer `location /pb/` Proxy erschien nie über HTTPS,
`nginx -T | grep -c "location /pb/"` = 0, obwohl die available-Datei den Block hatte.
**Diagnose (Symlink oder separate Datei?):**
```bash
ls -la /etc/nginx/sites-enabled/DOMAIN # "-> sites-available/..." = Symlink, sonst separate Datei
nginx -T 2>/dev/null | grep -c "location /pb/" # 0 obwohl in available vorhanden => separate Datei aktiv
```
**Fix:** Endversion nach BEIDEN `sites-available/...` UND `sites-enabled/...` schreiben
(oder sauber als Symlink erzwingen: `ln -sf .../sites-available/DOMAIN .../sites-enabled/DOMAIN`),
dann `nginx -t && systemctl reload nginx` und mit `nginx -T | grep` verifizieren, dass der
neue Block wirklich in der aktiven Config steht.
### ⚠️ PITFALL – write_file verweigert Systempfade
`write_file` kann **nicht** nach `/etc/nginx/sites-available/` schreiben (Systempfad-Schutz). **Workaround:** `sudo tee` im Terminal nutzen.
```bash
sudo tee /etc/nginx/sites-available/DOMAIN.de << 'EOF'
server {
listen 80;
listen [::]:80;
server_name DOMAIN.de www.DOMAIN.de;
root /var/www/DOMAIN.de/html;
index index.html index.htm;
access_log /var/www/DOMAIN.de/logs/access.log;
error_log /var/www/DOMAIN.de/logs/error.log;
location / {
try_files $uri $uri/ =404;
}
}
EOF
# Symlink aktivieren
sudo ln -sf /etc/nginx/sites-available/DOMAIN.de /etc/nginx/sites-enabled/
# Test + Reload
sudo nginx -t && sudo systemctl reload nginx
```
**Template** für neue VHosts liegt in `templates/nginx-vhost.conf`.
---
## Schritt 5: Lokal testen (Host-Header-Trick)
Auch wenn DNS noch nicht propagiert ist, kann man den VHost lokal testen:
```bash
# HTTP-Status prüfen
curl -s -o /dev/null -w "HTTP %{http_code}\n" -H "Host: DOMAIN.de" http://localhost/
# Titel auslesen
curl -s -H "Host: DOMAIN.de" http://localhost/ | grep -oP '<title>\K[^<]+'
```
**Erwartet:** HTTP 200 + korrekter Seitentitel.
---
## Schritt 6: Firewall (UFW) konfigurieren
```bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 22/tcp # SSH nicht vergessen!
sudo ufw --force enable
sudo ufw status verbose
```
⚠️ **PITFALL:** `ufw enable` ohne vorher `allow 22/tcp` sperrt dich aus dem SSH aus! Immer zuerst SSH erlauben.
---
## Schritt 7: Dem Nutzer DNS-Anpassungen mitteilen
Dem Nutzer eine klare Tabelle zeigen, welche DNS-Änderungen nötig sind:
| Domain | Aktuelles DNS | Aktion |
|--------|--------------|--------|
| domain.de | 104.21.x.x (Cloudflare) | A → SERVER-IP, Proxy deaktivieren |
| domain2.de | kein Eintrag | A-Record → SERVER-IP anlegen |
**Server-IP:** Immer explizit nennen (z. B. `92.60.38.175`).
---
## Schritt 8: SSL (nach DNS-Propagation)
Sobald DNS propagiert ist (5-60 Min):
```bash
sudo certbot --nginx -d DOMAIN.de -d www.DOMAIN.de --non-interactive --agree-tos --email ADMIN-EMAIL
```
Oder alle auf einmal:
```bash
sudo certbot --nginx -d domain1.de,www.domain1.de -d domain2.de,www.domain2.de ...
```
Certbot modifiziert automatisch die nginx-Konfiguration und fügt den `listen 443 ssl` Block hinzu.
**Zusätzlich:** Nach JEDEM certbot-Lauf für eine NEUE Domain müssen ALLE bestehenden Domains auf SSL-Fehler geprüft werden (certbot kann SSL-Pfade in bestehenden vhosts überschreiben). Siehe Pitfall unten.
### ⚠️ PITFALL – Certbot überschreibt SSL-Pfade in BESTEHENDEN vhosts
`certbot --nginx` für eine NEUE Domain kann die `ssl_certificate`-Pfade in den Configs
**bestehender vhosts überschreiben** — besonders wenn `server_name`-Wildcards (`*`) oder
Shared-SSL-Zertifikate im Spiel sind. Dieses Problem trat auf dem Mehlzoerwer-Mesh auf:
Nach `certbot --nginx -d git.mehlzoerwer.de` zeigte `mehlzoerwer.de` plötzlich auf das
`git.mehlzoerwer.de`-Zertifikat (ohne `mehlzoerwer.de` als SAN → `ERR_CERT_COMMON_NAME_INVALID`).
**Prävention — Pflicht nach jedem certbot-Lauf:**
```bash
# 1. ALLE bestehenden Domains auf SSL prüfen
for d in $(nginx -T 2>/dev/null | grep 'server_name ' | awk '{print $2}' | tr -d ';' | sort -u); do
code=$(curl -sk -o /dev/null -w "%{http_code}" "https://$d/" --max-time 5)
echo "$d → HTTP $code"
done
# 2. Wenn eine alte Domain 000 oder SSL-Fehler liefert → vhost-Checker
for f in /etc/nginx/sites-enabled/*; do
cert=$(grep ssl_certificate "$f" | head -1 | grep -oP '/live/\K[^/]+')
domain=$(grep server_name "$f" | head -1 | awk '{print $2}' | tr -d ';')
echo "$domain → Zertifikat: $cert"
done
```
**Reparatur:** `ssl_certificate` und `ssl_certificate_key` im betroffenen vhost zurück
auf das richtige Zertifikat setzen (z.B. `/etc/letsencrypt/live/mehlzoerwer.de/...`)
→ `nginx -t && systemctl reload nginx` → erneuter SSL-Test.
### ⚠️ PITFALL – Certbot-Henne-Ei bei NEUEM vhost (Zertifikat existiert noch nicht)
Wenn du den SSL-vhost **vorab** manuell anlegst (Referenz auf `/etc/letsencrypt/live/DOMAIN/fullchain.pem`), aber das Zertifikat noch nicht existiert, schlägt **`nginx -t` fehl** (`[emerg] cannot load certificate ... No such file or directory`). Dadurch schlägt **auch `certbot --nginx` fehl** (es läuft intern `nginx -t` → MisconfigurationError) — ein klassisches Henne-Ei.
**Fix – nicht einen SSL-vhost vorab anlegen, erst Zertifikat mit HTTP-only-vhost holen:**
1. Temporär einen **rein-HTTP-80-vhost** anlegen (nur `listen 80;`, kein SSL-Referenz):
```bash
# in sites-available: nur listen 80, server_name, root
sudo ln -sf .../sites-available/DOMAIN .../sites-enabled/ # oder in beide Dateien
sudo nginx -t && sudo systemctl reload nginx # JETZT grün
```
2. Zertifikat per **webroot** holen (port-80-challenge, kein nginx-Plugin nötig):
```bash
sudo mkdir -p /var/www/DOMAIN.de/.well-known
sudo certbot certonly --webroot -w /var/www/DOMAIN.de -d DOMAIN.de --agree-tos --register-unsafely-without-email
```
3. Erst **danach** den vollständigen SSL-vhost (mit `listen 443 ssl` + cert-Pfaden) ablegen + `nginx -t && systemctl reload`.
4. Verifizieren: `curl -sk https://DOMAIN.de` → HTTP 200 oder zumindest der SSL-Handshake ok (`curl -sv ... | grep "SSL certificate verify ok"`).
**Reihenfolge ist entscheidend:** Erst Port-80 + Zertifikat existiert, DANN SSL-vhost. Vorab-SSL-vhost = Henne-Ei-Blocker.
### ⚠️ PITFALL – write_file verweigert /etc/systemd oder andere geschützte Pfade
Für systemd-Unit-Services (nicht nginx): `write_file` verweigert ebenso `/etc/systemd/system/`. Muster: In `/tmp` schreiben, dann `sudo cp /tmp/foo.service /etc/systemd/system/` + `systemctl daemon-reload`.
---
## Troubleshooting
| Symptom | Ursache | Fix |
|---------|---------|-----|
| HTTP 403 | Falsche Datei-Rechte | `chown -R www-data:www-data /var/www/` |
| HTTP 404 | index.html fehlt | Pfad in `root`-Direktive prüfen |
| „server_names_hash_bucket_size" | Zu viele/zu lange Domainnamen | `sudo tee -a /etc/nginx/nginx.conf` → `server_names_hash_bucket_size 64;` im http-Block |
| nginx startet nicht | Syntax-Fehler | `sudo nginx -t` zeigt die genaue Zeile |
| write_file „sensitive system path" | Systempfad-Schutz | `sudo tee` im Terminal nutzen |
| ufw inactive | Firewall nie aktiviert | `sudo ufw --force enable` |