
Wenn du dich gerade mit einem VPS für NestJS/Next.js beschäftigst, bist du wahrscheinlich über die üblichen Inhalte gestolpert: „Nginx installieren, Node starten, Domain drauf, fertig.“ Und ja, so bekommst du eine App online. Was dir diese Artikel aber selten geben, ist das, wonach Menschen mit der höchsten Kaufbereitschaft tatsächlich suchen: „Wie sorge ich dafür, dass das Setup stabil läuft, mit Alerts, Logs, Backups und einem Plan, wenn es schiefgeht?“ Genau dort liegt der Unterschied zwischen einem Server, der „irgendwie läuft“ und einem Server, der dein Business trägt.
Dieser Artikel ist deshalb keine Deploy-Anleitung, sondern ein Production-Stack, opinionated, pragmatisch und kopierbar. Du baust dir ein Drei-Ebenen-Monitoring (extern, services, host), richtest Logs so ein, dass du Fehler in Minuten statt Stunden findest, machst Datenbank-Backups so, dass du sie wirklich restoren kannst, härtst SSH ohne dich auszusperren, konfigurierst Nginx/PM2 für Deploys ohne 502-Schock und am Ende hast du ein Incident-Runbook, das dich im Ernstfall wie ein Geländer stützt.
Wenn du nur eine Kernaussage mitnimmst: Stabilität ist kein Feature, das du „später“ nachrüstest. Stabilität ist eine Kette und sie reißt immer am schwächsten Glied. In der Praxis sind das fast immer fehlende Alerts, ungetestete Backups, offene SSH-Türen, Log-Wildwuchs und ein fehlender Plan, wenn das Pager-Icon (oder Slack-Ping) aufleuchtet.
Wenn du schon mal eine Nacht hattest, in der du morgens aufwachst, Slack öffnest und der erste Gedanke ist „Bitte nicht…“, dann weißt du, dass Monitoring nicht einfach nur "nice to have" ist. Monitoring ist Seelenfrieden. Der häufigste Grund, warum VPS-Projekte instabil wirken, ist selten ein einzelner großer Bug. Es ist ein kleines, fieses Bündel aus Dingen: ein Prozess hängt, das Zertifikat läuft ab, die Platte läuft voll, ein Deploy verursacht kurzzeitig 502 oder der Provider hat Netzwerkzuckungen. Genau deshalb brauchst du Monitoring als Frühwarnsystem, bevor der Schaden im Business ankommt.
Das sauberste mentale Modell für Node-Apps auf einem VPS ist ein Drei-Ebenen-Monitoring für außen, innen und unten. Außen bedeutet: Was sieht der Nutzer? Innen bedeutet: Leben die Abhängigkeiten (DB/Redis/Queue)? Unten bedeutet: Wie geht es dem Host (CPU/RAM/Disk/Netzwerk)?
Warum ist diese Dreiteilung so wichtig? Weil du sonst im Incident die falschen Fragen stellst. Wenn nur „Host CPU hoch“ alertet, weißt du nicht, ob Nutzer betroffen sind. Wenn nur „Homepage 200 OK“ alertet, merkst du nicht, dass deine DB langsam stirbt. Gute Stabilität heißt, dass du in 30 Sekunden einordnen kannst, was kaputt ist.
Für die externe Ebene ist ein Tool wie Uptime Kuma beliebt, weil du es sehr schnell selbst hosten kannst und viele Monitor-Typen bekommst. Die offizielle Install-Anleitung zeigt ein sehr direktes Docker-Run-Beispiel und genau so willst du es in einem VPS-Stack. Klein, klar und reproduzierbar.
docker run -d --restart=unless-stopped \
-p 3001:3001 \
-v uptime-kuma:/app/data \
--name uptime-kuma \
louislam/uptime-kuma:2Wenn du Docker hier nutzt, ist --restart=unless-stopped eine pragmatische Wahl, weil du im Normalfall willst, dass Monitoring wiederkommt, wenn der Host rebootet. Docker dokumentiert Restart-Policies und auch, wie sie sich in bestimmten Situationen verhalten (z. B. wenn du Container manuell stoppst). Genau diese „kleinen“ Details sind oft der Unterschied zwischen: „Nach dem Reboot war alles wieder da“ und „Warum war mein Monitoring ausgerechnet dann down, als ich es gebraucht hätte?“
Ein weiterer Vorteil von Uptime-Kuma ist, dass das Projekt selbst warnt, dass Filesystem-Support für POSIX-File-Locks wichtig ist, um SQLite-Corruption zu vermeiden. Er sagt also, leg dein Data-Volume nicht auf ein lustiges NFS-Konstrukt. Wenn du /app/data sauber auf eine lokale Disk oder ein lokales Volume mappst, bist du bei diesem Klassiker auf der sicheren Seite.
Was solltest du nun monitoren? Nicht nur „/“. Monitor das, was dir im Business wirklich weh tut. Idealerweise hast du in NestJS einen Health-Endpoint wie /healthz, der nur dann 200 liefert, wenn App und zentrale Abhängigkeiten okay sind. Dazu kommt ein „echter“ Pfad, der den Userflow trifft, wie etwa /login oder /api/auth/session oder ein Read-Only-Endpunkt, der wirklich durch deine gesamte Kette geht. Monitore auch die Zertifikatsablaufzeit, weil das sonst immer am Sonntagabend auffällt.
Jetzt zur Host-Ebene, die viele unterschätzen. Wenn du auf einem Server ein stabiles System willst, kommst du an „Host Metrics“ nicht vorbei. Der Standard-Baustein dafür ist der Node Exporter, der (offiziell dokumentiert) eine große Bandbreite an hardware- und kernel-nahen Metriken bereitstellt, die von Prometheus gescraped werden können.
Du musst dafür nicht sofort das ganze „Prometheus + Grafana“-Universum aufziehen. Du kannst klein anfangen. Aber klein heißt nicht „blind“. Klein heißt: Du wählst die Alerts, die dich vor echten Katastrophen schützen. In der Praxis sind das fünf Dinge, die fast immer die größten Feuer auslösen: Disk-Füllstand, RAM/Memory-Pressure, anhaltend hohe CPU/Load, HTTP 5xx und Latenz-Ausreißer (P95/P99).
Monitoring sagt dir, dass es brennt. Logs sagen dir, warum brennt es genau und wo ist der Brandherd? Ohne Logs debugst du im Nebel. Mit schlechten Logs debugst du im Nebel und verlierst dabei noch deine Festplatte. Auf einem Server gehören „Disk full wegen Logs“ und „Backups schlagen fehl, weil Disk voll ist“ zu den häufigsten Kettenreaktionen überhaupt und genau deshalb ist ein Log-Stack nicht Luxus, sondern Stabilitäts-Fundament.
Du willst, dass Logs wie eine gute Buchhaltung funktionieren. Du willst sie finden, du willst sie filtern, du willst sie rotieren, du willst sie aufbewahren und du willst nicht, dass sie dein System auffressen.
Der pragmatische Weg ist zweistufig. Erst lokal sauber, dann zentral, wenn du es wirklich brauchst. Lokal bedeutet: Nginx Access/Error Logs, Node-App-Logs über stdout/stderr und systemd/journal als Grundlage. Wenn du für Node PM2 nutzt, ist Log-Rotation praktisch Pflicht. PM2 dokumentiert selbst das Logrotate-Modul und nennt explizit den Installationsweg, denn so verhinderst du zu große Logfiles in Production.
pm2 install pm2-logrotateWenn du PM2 einsetzt, willst du außerdem dass deine Logs immer abrufbar sind und dass deine Prozesse auch einen Reboot überleben. PM2 beschreibt, dass es Startup-Skripte generieren kann, um deine Prozessliste über Restarts hinweg intakt zu halten.
pm2 start ecosystem.config.js --env production
pm2 save
pm2 startupWenn du PM2 nutzt, hat es eine eigene Log-Welt. Du kannst deine Logs live verfolgen mit
pm2 logsDu öffnest die Dateien typischerweise im PM2-Home und wenn du systemd nutzt, ist journalctl dein Freund. PM2 beschreibt außerdem explizit, dass Logs standardmäßig in ~/.pm2/logs liegen und dass du sie live streamen kannst.
Jetzt zur zweiten Stufe: zentrale Logs. Die brauchst du nicht, wenn du einmal im Monat debugst und du allein bist. Du brauchst sie, wenn du häufiger debugst, mehrere Services hast oder wenn du dich dabei ertappst, in SSH-Sessions herumzuspringen, bis du den richtigen Server gefunden hast. Ein gängiger Ansatz ist hier Loki, weil es (vereinfacht gesagt) nicht den ganzen Logtext indexiert, sondern über Labels arbeitet, was das Betreiben oft günstiger macht.
Ein wichtiger Hinweis für alle, die ihren Log-Stack heute neu aufsetzen: Promtail ist inzwischen deprecated. Grafana führt Promtail seit dem 13. Februar 2025 nur noch im LTS-Modus weiter, erwartet das End-of-Life am 2. März 2026 und empfiehlt für neue Setups klar Alloy als primären Collector für Logs nach Loki. Wenn du also neu startest, plane direkt mit Alloy. Wenn du bereits Promtail im Einsatz hast, musst du nicht hektisch alles umbauen, aber du solltest die Migration bewusst einplanen, statt deinen Logging-Weg auf eine auslaufende Komponente zu stützen.
Wenn du Logs debuggen willst, willst du typische Fragen schnell beantworten. Warum habe ich 502? Dann schaust du in Nginx error.log, weil dort die „Upstream refused/timeout“-Hinweise liegen. Warum ist die App plötzlich weg? Dann checkst du PM2-Status und die letzten Zeilen, denn viele Crashs sind in den letzten 20 Loglines schon erklärt. Warum sind Logs „plötzlich weg“? Dann ist es meistens Rotation/Retention. Warum ist Disk voll? Dann ist es meistens Logwachstum plus Backups plus Docker-Layer plus Journald.
Wenn du einen einzigen opinionated Best Practice Satz in deinen Kopf tätowieren willst: Logs brauchen ein Budget. Setz dir eine Grenze. Entscheide bewusst, wie viele Tage du lokal behältst? Wie groß darf ein Log werden? Was darf nie in Logs landen (Secrets, Tokens, komplette Request-Bodys)? Und dann setzte es technisch um, zuerst lokal mit Rotation, später zentral mit einem Collector.
Backups sind ein seltsames Thema. Jeder weiß, dass man sie braucht. Und trotzdem fehlen sie in erschreckend vielen VPS-Setups. Warum? Weil Backups nicht sofort Erfolgserlebnisse liefern. Sie liefern erst dann Erfolgserlebnisse, wenn alles schiefgeht und dann ist es zu spät, sie noch schnell zu bauen.
Für Node-Apps ist die Datenbank oft der eigentliche Single Point of Failure und „Backup“ bedeutet nicht „irgendwo liegt eine Datei“, sondern: Ich kann reproduzierbar auf einer frischen Maschine wiederherstellen, innerhalb einer Zeit, die mein Business akzeptiert.
Die klassische, extrem robuste Leitplanke ist die 3-2-1-Regel: drei Kopien, zwei verschiedene Medien, eine Kopie offsite. Sie ist so häufig zitiert, weil sie dir sofort den Denkfehler nimmt, dass du ja ein Backup auf demselben Server hast. In der Praxis heißt das, dass Daten auf dem VPS keine Backup-Kopie sind.
Ein pragmatisches zielbild für einen VPS ist, dass du ein tägliches logisches Backup der Datenbank machst, es komprimierst, lokal eine kurze Retention (z. B. 7 Tage) hältst und verschlüsselt offsite schiebst. Wenn du das gut machst, ist es nicht kompliziert, aber es ist konsequent.
Für MySQL ist mysqldump der Klassiker für logische Backups. Die MySQL-Doku beschreibt es sehr klar: mysqldump erzeugt SQL-Statements, die die Datenbankobjekte und die Table-Daten reproduzieren können. Genau das willst du für „einfacher Restore“.
Hier ist ein Backup-Skript, das du direkt auf einem Ubuntu-VPS nutzen kannst. Es ist bewusst simpel: eine Datenbank, ein gzipped Dump, Retention per find. Genau so fängt man an, nicht perfekt, aber stabil.
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/var/backups/mysql"
DB_NAME="app_db"
DATE="$(date +%F)"
FILE="${BACKUP_DIR}/${DB_NAME}_${DATE}.sql.gz"
mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"
mysqldump \
--single-transaction \
--routines \
--triggers \
--databases "$DB_NAME" \
| gzip -c > "$FILE"
find "$BACKUP_DIR" -type f -name "${DB_NAME}_*.sql.gz" -mtime +7 -delete
echo "Backup written: $FILE"Drei Details daran sind wichtig zu wissen. Erstens ist --single-transaction bei InnoDB eine bewährte Methode, um einen konsistenten Snapshot zu ziehen, ohne alles hart zu locken. Der mysqldump-Manpage-Text erwähnt genau dieses Online-Backup-Pattern für InnoDB.
Zweitens nimmst du --routines und --triggers mit, weil sonst bei einem Restore später etwas fehlt und das merkst du oft erst, wenn es brennt.
Drittens packst du das Backup in /var/backups/mysql und gibst dem Ordner restriktive Rechte, weil Backups hochsensibel sind.
Wie restore ich nun dieses Backup? Du entpackst auf stdout und fütterst den MySQL-Client. Das ist die häufigste Restore-Form bei gzip, denn sie ist schnell, klar und funktioniert.
gunzip -c app_db_2026-03-09.sql.gz | mysql -u root -pRestore-Tests sind übrigens nicht optional. Backup-Dateien existieren oft, ohne dass sie brauchbar sind. Falsche Credentials, kaputte Dumps, fehlende Permissions, fehlende Routines, falsche Charset/Collation oder zu wenig Disk beim Restore sorgen dafür, dass manche Backups gar nichts bringen. Deshalb ist der „Fire-Drill“ einmal im Monat so ein kraftvoller Best Practice. Du nimmst eine frische VM oder einen frischen Container, spielst den letzten Dump ein, machst einen Smoke-Test (Login, zwei Kern-Endpunkte) und du misst die Zeit. Genau so bekommst du ein Gefühl für RTO/Realität.
Wenn du noch einen Schritt weiter gehen willst, gibt es die Point-in-Time Recovery. Das brauchst du dann, wenn „Stand letzte Nacht“ nicht reicht, weil jemand um 15:17 Uhr aus Versehen einen Delete gemacht hat und du den Zustand kurz davor wiederhaben willst. Die MySQL-Doku beschreibt PITR über Binary Logs und die Utility mysqlbinlog, inklusive der Möglichkeit, Teile der Binlogs nach Zeiten oder Positionen auszuwählen und anzuwenden.
Offsite ist der nächste Stabilitätshebel. Offsite heißt nicht nur wegkopieren, sondern auch nicht lesbar, wenn der Storage kompromittiert wird. Genau hier glänzt restic, weil es als Backup-Tool mit verschlüsseltem Repository gebaut ist und sich auf verschiedene Backends legen lässt. Die restic-Doku beschreibt das Init eines Repositories und dass es lokal oder remote liegen kann. Der praktische Effekt für dich ist, dass du es Offsite machen kannst, ohne selbst die Daten verschlüsseln zu müssen.
Ein minimalistischer Start, der wirklich realistisch ist, wäre folgender. Du installierst restic auf dem VPS, initialisierst ein Repository auf einem S3-kompatiblen Bucket oder einem SFTP-Ziel und pushst deine Backup-Dateien dorthin. Restic betont explizit, dass du ohne Passwort nicht an die Daten kommst und dass verlorenes Passwort nicht recoverable ist. Das ist unbequem, aber genau das ist Security.
Wenn du dir dafür ein „copy/paste“-Grundgerüst wünschst, ist das hier ein brauchbarer, sehr minimalistischer Start für ein S3-Repo (Repository-URL und Credentials musst du natürlich anpassen):
export RESTIC_REPOSITORY="s3:s3.amazonaws.com/your-bucket-name"
export RESTIC_PASSWORD="CHANGE-ME-TO-A-LONG-PASSPHRASE"
restic init
restic backup /var/backups/mysqlDas Schöne ist, dass du mit so einem Setup sofort starten und später Retention, Forget/Prune und Monitoring hinzufügen kannst. Es muss nicht perfekt sein, aber es sollte existieren und Offsite sein.
Es gibt zwei Arten, wie Menschen SSH-Hardening angehen. Sie lassen alles wie es ist, bis sie Bots im Log sehen oder sie ändern drei Optionen, drücken Enter und sperren sich aus. Du willst weder noch. Du willst: Konsequent sicher, aber sicher umsetzbar.
Wenn dein VPS im Internet hängt, wird er gescannt. Das ist kein Gefühl, das ist Realität. Genau deshalb ist SSH-Hardening der größte Hebel pro investierter Minute. Du schneidest eine komplette Angriffsklasse ab, indem du Password-Login eliminierst und Root-Zugang dicht machst.
Der Kern dreht sich um ein paar Direktiven in der SSHD-Konfiguration. Die manpages und Ubuntu-Manpages beschreiben sehr konkret, was Optionen wie PermitRootLogin bedeuten (inklusive der Varianten wie prohibit-password) und wie Password-Authentifizierung funktioniert.
Die wichtigste Regel, die du in deinem Kopf behalten solltest, ist simpel und rettet dich vor dem Klassiker: Lass eine bestehende SSH-Session offen, öffne eine zweite Session zum Testen, erst wenn die zweite Session funktioniert, schaltest du Password-Login ab. Genau dieser Satz ist so banal, dass viele ihn vergessen und so entscheidend, dass man ihn immer im Hinterkopf behalten sollte.
Ich empfehle außerdem: Nutze auf modernen Ubuntu-Versionen eine Drop-in-Datei statt die Hauptdatei zu zerlegen. Das ist kein Muss, aber es macht deine Änderungen sauber und versionierbar. Du legst also z. B. eine Datei unter /etc/ssh/sshd_config.d/ an:
sudo nano /etc/ssh/sshd_config.d/99-hardening.confUnd hier ein konservatives Set, das für viele VPS-Setups als Startpunkt funktioniert:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowUsers deployBevor du reloadest, testest du die Konfiguration. Das ist die zweite Anti-Lockout-Regel. Erst validieren, dann laden.
sudo sshd -t
sudo systemctl reload sshDass sshd -t ein valider Weg ist, die Konfiguration zu prüfen, bevor du Änderungen aktivierst, ist eines dieser kleinen Handgriffe, die im Ernstfall Gold wert sind, weil du sonst im schlimmsten Fall einen nicht mehr erreichbaren Server hast.
Jetzt zur Firewall. Viele denken „SSH absichern“ heißt nur „Passwörter aus“. Aber Network Exposure ist genauso wichtig. Ubuntu dokumentiert ufw als Standard-Tool, um Firewall-Regeln nutzerfreundlich zu verwalten. Der entscheidende Beginner-Fail ist dabei, die Firewall zu aktivieren, bevor SSH erlaubt ist. Genau deshalb solltest du es immer in dieser Reihenfolge machen: Default deny incoming, outgoing erlauben, SSH erlauben, HTTP/HTTPS erlauben, dann aktivieren.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verboseUnd dann ist da noch das Thema „Bots ausbremsen“. Hier kommt Fail2Ban ins Spiel. Fail2Ban scannt Logfiles wie /var/log/auth.log und bannt IPs, die zu viele Fehlversuche erzeugen, indem es Firewall-Regeln aktualisiert. Das ist kein Ersatz für Key-only SSH, aber es ist ein sehr gutes Schutzgitter gegen unnötigen Lärm und Bruteforce-Spam.
Ein letzter Security-Baustein, der oft „vergessen“ wird, sind automatische Security Updates. Ubuntu dokumentiert, dass Security Updates automatisch angewendet werden (über unattended-upgrades) und beschreibt, welche Optionen es gibt, bis hin zu automatischen Reboots, wenn nötig. Ganz wichtig ist dabei, auch wenn Pakete installiert sind, solltest du einmal prüfen, ob es wirklich aktiv ist und du solltest bewusst entscheiden, ob du Reboots automatisch zulässt oder lieber in Wartungsfenstern planst.
Wenn du das sauber aktivieren willst, ist das hier in vielen Fällen ausreichend:
sudo apt update
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgradesNginx ist in deinem Stack nicht nur „Reverse Proxy“. Nginx ist dein Türsteher. Es sitzt vorne, terminiert TLS (oder leitet weiter), kann Payload-Größen limitieren, kann Rate Limiting machen, kann Buffering sinnvoll konfigurieren und ist damit genau der Ort, an dem du viele Security- und Stabilitätsprobleme abfängst, bevor sie deine Node-Prozesse überhaupt erreichen. Die NGINX-Dokumentation beschreibt Reverse-Proxy-Konfiguration als Kernaufgabe inklusive dem Modifizieren von Request-Headern und Response-Buffering.
Beim Self-Hosting von Next.js Apps wird explizit empfohlen, einen Reverse Proxy wie Nginx vor den Next.js-Server zu setzen, statt ihn direkt ins Internet zu stellen. Unter anderem, weil der Reverse Proxy Dinge wie malformed requests, slow connection attacks, payload size limits oder rate limiting übernehmen kann.
„Warum 502 Bad Gateway nach Deploy?“ Wenn du das einmal verstanden hast, wirkt 502 plötzlich weniger wie ein Mysterium und mehr wie ein Symptom. Ein 502 von Nginx bedeutet im Alltag fast immer, dass Nginx mit dem Vorgang nicht sauber sprechen konnte. Die häufigsten Gründe sind banal. Dein Node-Prozess war beim Reload kurz weg, er lauscht auf einem anderen Port als gedacht, er ist gecrasht, er braucht beim Start länger als Nginx geduldig ist oder WebSockets/SSE sind falsch konfiguriert und es kommt zu Upgrades/Timeouts.
Deshalb ist ein solides Nginx-Server-Block-Template hilfreich. Es ist kein Allheilmittel, aber es ist ein stabiles Fundament, auf dem du iterierst. Es setzt die klassischen X-Forwarded-Header, aktiviert WebSocket-Upgrade-Header und setzt Timeouts in einem Bereich, der nicht sofort bei langsamen Requests zerbricht.
server {
listen 80;
server_name example.com;
client_max_body_size 20m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}Der nächste Klassiker sind HTTPS/Redirects, die sich komisch verhalten, Cookies, die nicht „secure“ sind und dass du Redirect-Loops bekommst. In Node-Stacks passiert das häufig, wenn dein App-Layer nicht korrekt versteht, dass der ursprüngliche Request HTTPS war, während intern (zwischen Nginx und Node) HTTP läuft. Hier sind X-Forwarded-Proto und „trust proxy“ der Schlüssel. Hinter Proxies solltest du die trust-proxy-Einstellung nutzen, damit Werte wie Client-IP und andere Request-Properties korrekt abgeleitet werden. Das ist nicht nice-to-have, das ist die Basis, damit deine App in Production nicht falsch wahrnimmt, was vom Client kam.
Auch bei Security Headers gilt: Bitte nicht einfach blind eine Copy-Paste-Wand aus irgendeinem Tutorial übernehmen. Genau da verliert ein Setup oft mehr Qualität, als es gewinnt. Der bessere Weg ist, klein anzufangen, die Wirkung zu verstehen und dann gezielt zu erweitern. Nicht jeder Header ist in jeder Konstellation sinnvoll, und mehr ist hier nicht automatisch sicherer.
Daneben spielt PM2 eine entscheidende Rolle für einen stabilen Node-VPS, weil es deine Prozesse nicht nur am Leben hält, sondern auch dafür sorgt, dass deine App nach einem Reboot überhaupt wieder hochkommt. Genau das ist einer der unsichtbaren Unterschiede zwischen „läuft irgendwie“ und „läuft stabil“. Wenn Nginx sauber weiterleitet, aber PM2 nicht richtig als Startup-Dienst eingerichtet ist, wirkt dein Server nach außen instabil, obwohl das eigentliche Problem nur ein unvollständig gedachtes Prozess-Setup ist.
Was viele im Production-Alltag rettet, ist der Memory Threshold Auto Restart. PM2 dokumentiert, dass du Prozesse automatisch reloaden/restarten kannst, wenn sie eine Memory-Schwelle erreichen. Das behebt keinen Memory Leak. Aber es verhindert, dass ein Leak deine App hart offline nimmt, während du noch nicht mal die Ursache gefunden hast. Und genau deshalb ist es für kleine Teams ein sehr sinnvolles Stabilitäts-Tool.
So kannst du das aktivieren:
pm2 start "npm run start" --name app --max-memory-restart 400M
pm2 saveAber nicht vergessen, das ist ein Airbag, kein Fix. Wenn du diesen Restart siehst, ist das ein Signal, dass du Profiling und Memory-Analyse brauchst oder dass du ein Underprovisioning-Problem hast.
Das Thema 502 nach Deploy löst du in der Praxis meist nicht mit einem Nginx-Trick, sondern mit Deploy-Disziplin. Der wichtigste Hebel ist ein Zero-Downtime Mindset. Das heißt, du stoppst den Prozess nicht hart, du reloadest geordnet, du gibst deinem Prozess Zeit, Requests sauber zu beenden und du sorgst dafür, dass neue Instanzen bereit sind, bevor die alten sterben. Dafür kann PM2 in Cluster-Mode helfen, alternativ kann systemd mit zwei Instanzen helfen oder du nutzt „blue/green“ per Port-Switch. Welchen Weg du wählst, hängt von deinem Setup ab, aber der Kern ist immer: Der Vorgang darf nicht kurz verschwinden, während Nginx noch draufzeigt.
Hier ist ein Runbook-Flow, den du dir wirklich einprägen kannst. Er ist bewusst niedrigschwellig, weil du im Alarmfall nicht „komplex“ brauchst, sondern „klar“.
Der erste Schritt ist der wichtigste: Scope. Statt Logs zu durchwühlen, siehst du direkt, wie groß das Feuer ist. Ist das System komplett down (kein TCP/HTTP)? Ist es degraded (slow, 5xx)? Oder betrifft es nur eine Route? Wenn du das einmal sauber trennst, verhinderst du zwei Stunden DB-Debugging, obwohl die Platte voll war.
Jetzt brauchst du ein 5-Minuten-Diagnose-Set. Das sind die wenigen Commands, die du in jeder Situation laufen lassen kannst, um 80% der Ursachen zu identifizieren. Diese Commands sind nicht glamourös, aber sie sind genau das, was du im Alarmfall willst: immer gleich, immer schnell, immer ein Signal.
# Host-Schnellcheck
uptime
df -h
free -h
# Nginx-Schnellcheck
sudo nginx -t
sudo systemctl status nginx --no-pager
sudo tail -n 80 /var/log/nginx/error.log
# PM2/Node-Schnellcheck
pm2 status
pm2 logs --lines 120Und weil die häufigsten Incidents im VPS-Kontext sehr wiederkehrend sind, kannst du im Artikel (und in deinem eigenen Kopf) drei typische Patterns trainieren.
Pattern eins ist „502 Bad Gateway nach Deploy“. Du siehst 502-Spikes direkt nach einem Restart. In Nginx error.log findest du Hinweise wie „connect() failed“, „upstream timed out“ oder „refused“. Das liegt fast nie daran, dass NGINX einfach „spinnt". Es ist fast immer „Upstream war kurz nicht da“ oder „Upstream antwortet zu langsam“. Die Mitigation ist entsprechend: sofort Rollback oder Neustart auf einen vorherigen Build, oder ein geordneter Reload und ggf. Timeouts anpassen und langfristig Deploy-Flow so bauen, dass der Upstream nicht verschwindet (Cluster/Reload/Graceful shutdown).
Pattern zwei ist „Alles ist langsam“. Du siehst hohe Load, hohe CPU, Memory Pressure oder langsam werdende DB-Queries. Die Mitigation kann sein: Traffic drosseln (Rate Limit), Worker runterfahren, einen schweren Cronjob stoppen, Cache aktivieren oder kurzfristig vertical scaling. Langfristig ist die Antwort, dass du Metriken (Host + App) brauchst, damit du unterscheiden kannst, ob CPU, RAM, Disk IO oder DB der Bottleneck ist.
Pattern drei ist „Plötzlich down“. Sehr oft ist das Disk full, ein abgelaufenes Zertifikat oder ein Reboot ohne Autostart. Das klingt banal, ist aber genau die Realität. Und genau deshalb ist dein Stack so gebaut: Disk-Alerts, TLS-Monitoring, PM2-startup, unattended upgrades bewusst konfiguriert.
Nach dem Vorfall kommt der Teil, der dich langfristig stabil macht: das Mini-Postmortem. Du brauchst keinen Roman. Du brauchst einen knappen, tadellosen Ablauf. Was war Trigger? Was war Root Cause? Was hat geholfen, was hat gefehlt? Und welche 1–3 Action Items verhindern Wiederholung? Diese Vorgehensweise sorgt dafür, dass du nicht zweimal an derselben Stelle bluten musst.
Wenn dir dieser Artikel gefallen hat und du weitere interessante Blogartikel zu ähnlichen Themen lesen möchtest, dann abonniere meinen Newsletter. Ich halte dich dann immer auf dem laufenden.
Kommentare
Bitte melde dich an, um einen Kommentar zu schreiben.