nginx, certbot und ein Dienst, der Neustarts übersteht
Vom „läuft auf Port 3000“ zur echten Adresse mit Zertifikat. Reverse Proxy, automatische Zertifikate, Prozessverwaltung — und die Fehler, die ich dabei zweimal gemacht habe.
955 Wörter · 5 Min. Lesezeit
- infrastruktur
- linux
- deployment
Das Dashboard lief auf meinem Rechner unter localhost:3000. Damit es unter einer echten Adresse mit Schloss-Symbol erreichbar wird, fehlten drei Dinge: ein Prozess, der dauerhaft läuft, ein Server, der Anfragen entgegennimmt, und ein Zertifikat.
Ich habe das damals über ein Wochenende zusammengesucht, aus fünf Anleitungen, von denen keine ganz gepasst hat. Deshalb hier einmal der ganze Weg zusammenhängend.
Der Reverse Proxy
Die Anwendung selbst soll nicht direkt aus dem Internet erreichbar sein. Sie lauscht auf 127.0.0.1:3000, und davor steht nginx, der Anfragen von außen annimmt und weiterreicht.
server {
listen 80;
server_name beispiel.de;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
}
}Die fünf proxy_set_header-Zeilen sind der Teil, den ich beim ersten Mal weggelassen habe, weil es auch ohne funktionierte. Es funktionierte allerdings falsch:
- Ohne
Hostbaut die Anwendung Links mit der internen Adresse. - Ohne
X-Forwarded-Protodenkt sie, alles laufe über HTTP, und erzeugt Weiterleitungen aufhttp://, obwohl der Nutzer aufhttps://ist. Bei einer Anmeldung führt das zu einer Schleife. - Ohne
X-Real-IPsieht jede Anfrage im Protokoll so aus, als käme sie von127.0.0.1. Das merkt man in dem Moment, in dem man zum ersten Mal wissen will, wer da eigentlich anfragt. - Ohne die beiden Upgrade-Zeilen funktionieren WebSockets nicht.
Das Zertifikat
Mit certbot ist das ein Befehl:
certbot --nginx -d beispiel.de -d www.beispiel.decertbot trägt die Zertifikatspfade selbst in die nginx-Konfiguration ein und richtet die Erneuerung ein. Zertifikate von Let's Encrypt laufen nach neunzig Tagen ab, die Erneuerung passiert automatisch etwa dreißig Tage vorher.
Was ich mir dabei angewöhnt habe: einmal ausdrücklich prüfen, dass die Erneuerung wirklich funktioniert, statt es zu glauben.
certbot renew --dry-runDas ist derselbe Gedanke wie bei Sicherungen: Ein Automatismus, der nie geprüft wurde, ist eine Hoffnung.
Warnung
Let's Encrypt hat Ratenbegrenzungen. Wer beim Einrichten fünfmal hintereinander ein Zertifikat für dieselbe Domain zieht, weil etwas nicht klappt, ist danach für eine Weile gesperrt — und zwar für diese Domain, nicht für den Server. Zum Ausprobieren gibt es eine Testumgebung (--staging), die keine gültigen Zertifikate ausstellt, aber alles andere gleich behandelt. Ich habe das erst nach der ersten Sperre gelernt.
Der Dienst, der von selbst wiederkommt
Für Node-Anwendungen habe ich damals einen Prozessmanager genommen, der Neustarts, Logs und Autostart übernimmt:
pm2 start npm --name dashboard -- start
pm2 save
pm2 startupDer letzte Befehl ist der wichtigste und wird am häufigsten vergessen: Er richtet den Autostart beim Systemstart ein. Ohne ihn ist nach dem nächsten Neustart des Servers alles weg — was man typischerweise vier Wochen später um zwei Uhr nachts feststellt.
Zwei Dinge, die ich dabei gelernt habe:
Ein Neustart darf nicht bauen. Meine erste Konfiguration hat beim Start die Anwendung neu gebaut. Ein Neustart dauerte damit Minuten statt Sekunden, und wenn der Bau fehlschlug, war der Dienst weg. Bauen und Starten sind zwei verschiedene Vorgänge, und der Start muss immer funktionieren.
Umgebungsvariablen werden gecacht. Wer eine .env ändert und den Prozess neu startet, hat unter Umständen weiterhin die alten Werte, weil der Manager sich die Umgebung vom ersten Start gemerkt hat. Dafür gibt es eine ausdrückliche Option — und einen Nachmittag Verwirrung, wenn man sie nicht kennt.
Die Firewall, die man dabei vergisst
Meine Anwendung lauschte anfangs auf 0.0.0.0:3000 statt auf 127.0.0.1:3000. Damit war sie zusätzlich direkt erreichbar — unter http://server-ip:3000, ohne Zertifikat und ohne die ganzen Kopfzeilen. Das ist kein theoretisches Problem: Suchmaschinen und Scanner finden solche Ports innerhalb von Tagen.
Zwei Absicherungen, beide notwendig:
ufw allow 'Nginx Full' # nur 80 und 443 von außenUnd in der Anwendung ausdrücklich an die lokale Adresse binden, statt an alle. Die Firewall ist die zweite Verteidigungslinie; die erste ist, gar nicht erst nach außen zu lauschen.
Die Fehler, die ich bei den Kopfzeilen gemacht habe
Zwei Sachen, die ich zuerst falsch hatte und die beide erst unter bestimmten Umständen auffallen.
Ich habe die Weiterleitungskopfzeilen angehängt statt gesetzt. Es gibt einen Unterschied zwischen „schreibe die Adresse hinein" und „hänge die Adresse an das an, was schon da steht". Bei einer einzelnen Zwischenschicht ist das egal. Sobald zwei hintereinander stehen, entsteht eine Liste — und wenn man die falsch auswertet, nimmt man die Adresse, die der Aufrufende selbst gesetzt hat. Damit kann sich jeder eine beliebige Herkunft ausdenken.
Ich habe die Größenbegrenzung für Uploads vergessen. Der Standardwert ist klein, und der Fehler, den man bekommt, deutet nicht auf eine Größenbegrenzung hin. Wer eine Datei hochlädt und einen unspezifischen Fehler bekommt, sucht in der Anwendung.
Was ich seitdem als Erstes einrichte
Bei jedem neuen Dienst folge ich derselben Reihenfolge, und sie hat sich als robust erwiesen:
- Dienst lokal starten, auf einer Adresse, die nur die Maschine selbst erreicht.
- Von der Maschine aus prüfen, ob er antwortet.
- Proxy-Eintrag anlegen, ohne Zertifikat, nur mit unverschlüsseltem Zugang.
- Von außen prüfen, ob die Weiterleitung stimmt.
- Zertifikat ziehen und Weiterleitung auf verschlüsselt erzwingen.
- Erneuerung trocken testen.
- Autostart einrichten und einmal neu starten.
Der siebte Punkt ist der, der am häufigsten fehlt — und der einzige, dessen Fehlen man erst Wochen später bemerkt, meistens nach einem ungeplanten Neustart.
Was ich mir zusätzlich angewöhnt habe: nach Schritt sieben einmal die gesamte Maschine neu starten und danach prüfen, was fehlt. Das dauert zwei Minuten und beantwortet eine Frage, die sonst irgendwann nachts gestellt wird.
Was ich mir seitdem angewöhnt habe
Nach diesem Wochenende habe ich mir eine Checkliste geschrieben, die ich bis heute für jeden neuen Dienst durchgehe:
- Lauscht der Dienst nur lokal?
- Steht ein Proxy davor, der die Kopfzeilen richtig setzt?
- Gibt es ein Zertifikat, und wurde die Erneuerung einmal trocken geprüft?
- Startet der Dienst nach einem Serverneustart automatisch?
- Baut der Start nichts, sondern startet nur?
- Sind alle Ports außer den nötigen dicht?
- Landet irgendwo ein Protokoll, das man später lesen kann?
Das klingt nach Bürokratie und dauert zehn Minuten. Ich habe später Dienste betrieben, bei denen ich diese Liste nicht durchgegangen bin, und jedes Mal war es genau einer dieser sieben Punkte, der mich Wochen später eingeholt hat.