Dokumentation aus dem Wiki uebernommen und um die Dienste auf dem Server
erweitert. Alle Anleitungen sind so geschrieben, dass sich der jeweilige
Dienst allein daraus neu aufsetzen laesst.
docs/ Dienste im Heimnetz (aus dem Wiki, anonymisiert)
services/ Dienste auf dem Server mit Konfigurationsvorlagen
- matrix-synapse: Homeserver, Postgres, Well-Known-Delegation
- coturn: TURN-Relay fuer Anrufe
- element-web: Web-Client
- wikijs, gitea, nginx
Durchgehend anonymisiert: echte Domain durch example.com ersetzt, IP-Adressen
und E-Mail-Adressen durch Platzhalter. Konfigurationsdateien liegen nur als
.example mit Platzhaltern statt echter Secrets vor.
Die Anleitungen halten die Stolpersteine fest, die beim Aufbau tatsaechlich
aufgetreten sind, unter anderem:
- Synapse verlangt LC_COLLATE=C, sonst startet es nicht
- server_name ist nachtraeglich nicht aenderbar -> Delegation noetig
- register_new_matrix_user liest conf.d nicht, Secret muss per -k kommen
- coturn braucht external-ip, sonst kommen keine Medien durch
- Gruppenmitgliedschaft fuer den Zertifikatszugriff wirkt erst beim Neustart
- nginx vererbt add_header nicht in location-Bloecke mit eigenen Direktiven
129 lines
3.6 KiB
Markdown
129 lines
3.6 KiB
Markdown
# nginx und TLS
|
||
|
||
nginx ist der einzige Dienst, der von außen erreichbar ist. Alle Anwendungen
|
||
lauschen nur auf `127.0.0.1` und werden von hier weitergereicht.
|
||
|
||
## Aufteilung
|
||
|
||
| Domain | Ziel |
|
||
|---|---|
|
||
| `example.com` | statische Seite + Matrix-Well-Known |
|
||
| `matrix.example.com` | Synapse auf `127.0.0.1:8008` |
|
||
| `element.example.com` | statische Dateien `/var/www/element` |
|
||
| `wiki.example.com` | Wiki.js auf `127.0.0.1:3000` |
|
||
| `git.example.com` | Gitea auf `127.0.0.1:3001` |
|
||
| `turn.example.com` | **kein** vHost – coturn nutzt das Zertifikat direkt |
|
||
|
||
`turn.example.com` läuft bewusst nicht über nginx. coturn spricht kein HTTP; es
|
||
braucht nur ein gültiges Zertifikat für Port 5349. Der Name muss trotzdem in
|
||
einem Zertifikat stehen, deshalb wird er beim Ausstellen mit angegeben.
|
||
|
||
## Zertifikate
|
||
|
||
Ein Zertifikat für mehrere Namen:
|
||
|
||
```bash
|
||
sudo certbot certonly --webroot -w /var/www/html \
|
||
-d matrix.example.com -d element.example.com -d turn.example.com
|
||
```
|
||
|
||
Für die Erstausstellung muss Port 80 die ACME-Anfrage beantworten können:
|
||
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name matrix.example.com element.example.com turn.example.com;
|
||
root /var/www/html;
|
||
location /.well-known/acme-challenge/ { allow all; }
|
||
}
|
||
```
|
||
|
||
Enthaltene Namen prüfen:
|
||
|
||
```bash
|
||
sudo openssl x509 -in /etc/letsencrypt/live/matrix.example.com/fullchain.pem \
|
||
-noout -ext subjectAltName
|
||
```
|
||
|
||
## Erneuerung
|
||
|
||
certbot erneuert automatisch. Dienste, die das Material **selbst** lesen (hier
|
||
coturn), merken das nicht – sie halten es seit dem Start im Speicher. Deshalb
|
||
ein Deploy-Hook nach
|
||
`/etc/letsencrypt/renewal-hooks/deploy/10-reload-services.sh`:
|
||
|
||
```sh
|
||
#!/bin/sh
|
||
set -e
|
||
chgrp -R ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive || true
|
||
chmod -R g+rX /etc/letsencrypt/live /etc/letsencrypt/archive || true
|
||
find /etc/letsencrypt/archive -name 'privkey*.pem' -exec chmod 640 {} \; || true
|
||
systemctl reload nginx || true
|
||
systemctl restart coturn || true
|
||
```
|
||
|
||
Trockenlauf:
|
||
|
||
```bash
|
||
sudo certbot renew --dry-run
|
||
```
|
||
|
||
## Zertifikate für Nicht-HTTP-Dienste freigeben
|
||
|
||
Standardmäßig darf nur `root` den privaten Schlüssel lesen. Statt die Rechte
|
||
global zu öffnen, eine Gruppe verwenden und nur die betroffenen Dienstbenutzer
|
||
aufnehmen:
|
||
|
||
```bash
|
||
sudo groupadd -f ssl-cert
|
||
sudo usermod -aG ssl-cert turnserver
|
||
|
||
sudo chgrp -R ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive
|
||
sudo chmod -R g+rX /etc/letsencrypt/live /etc/letsencrypt/archive
|
||
sudo find /etc/letsencrypt/archive -name 'privkey*.pem' -exec chmod 640 {} \;
|
||
```
|
||
|
||
Gruppenmitgliedschaften wirken erst beim Start eines Prozesses – danach ist ein
|
||
`restart` nötig, kein `reload`.
|
||
|
||
## Zwei wiederkehrende Fallstricke
|
||
|
||
**`add_header` wird nicht vererbt.** Sobald ein `location`-Block eine eigene
|
||
`add_header`-Direktive enthält, verliert er *alle* geerbten. Sicherheitsheader
|
||
gehören deshalb in ein Snippet, das in jeden betroffenen Block eingebunden wird:
|
||
|
||
```nginx
|
||
include /etc/nginx/snippets/element-security.conf;
|
||
```
|
||
|
||
**Zeitüberschreitungen bei Long-Polling.** Matrix-Clients halten `/sync` offen.
|
||
Mit dem Standardwert von 60 Sekunden bricht die Verbindung ständig ab:
|
||
|
||
```nginx
|
||
proxy_read_timeout 3600s;
|
||
proxy_send_timeout 3600s;
|
||
proxy_buffering off;
|
||
```
|
||
|
||
## Prüfen
|
||
|
||
```bash
|
||
sudo nginx -t
|
||
sudo systemctl reload nginx
|
||
|
||
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/
|
||
curl -s -o /dev/null -D - https://element.example.com/ | grep -iE '^x-|^strict'
|
||
```
|
||
|
||
## Firewall
|
||
|
||
Freizugeben: 80 und 443 TCP, dazu für coturn 3478 und 5349 (je TCP und UDP)
|
||
sowie 49152–65535 UDP.
|
||
|
||
```bash
|
||
sudo ufw allow 80,443/tcp
|
||
sudo ufw allow 3478,5349/tcp
|
||
sudo ufw allow 3478,5349/udp
|
||
sudo ufw allow 49152:65535/udp
|
||
```
|