Veröffentlicht: Juni 18, 2026 · Aktualisiert: Juni 23, 2026
Ein Sysadmin-Postmortem über einen nächtlichen Serverausfall, Minecraft und den OOM-Killer.
Der Alarm um Mitternacht
Es war kurz vor Mitternacht, als der erste Alert von meinem Monitoring-System eintraf. Load Average: über 100. Auf einem Server, der normalerweise bei 0,5–1,5 dahinläuft.
Der Server — nennen wir ihn vps-web — hostet mehrere Webseiten, eine MariaDB-Datenbank und einen Varnish-Cache. Kein Hochverkehrs-System. Gerade deshalb war ein Load von 100+ so beunruhigend.
Das Zeitfenster: ca. 23:45 Uhr bis 00:15 Uhr.
Begriffe kurz erklärt
Bevor wir in die Details gehen — ein paar Grundbegriffe für alle, die nicht täglich in Server-Logs wühlen.
📖 Load Average
Der Load Average (auf Deutsch: Systemlast) ist kein CPU-Auslastungswert — das ist ein weit verbreitetes Missverständnis. Er zeigt, wie viele Prozesse im Durchschnitt auf eine Ressource warten: die CPU, Festplattenzugriffe oder Speicher. Ein Wert von 1.0 bedeutet: ein Prozess wartet zu jedem Zeitpunkt. Ein Wert von 100 bedeutet, dass im Schnitt 100 Prozesse gleichzeitig blockiert sind.
Auf einem System mit 2 CPU-Kernen gilt ein Load > 2 als „ausgelastet“ — ein Load von 100 ist eine Katastrophe. uptime und top zeigen ihn als drei Zahlen: Durchschnitt der letzten 1, 5 und 15 Minuten.
📖 Swap & Swap-Thrashing
Swap ist Festplattenplatz, der als Notfall-RAM genutzt wird. Wenn echter RAM voll ist, verschiebt der Linux-Kernel selten genutzte Speicherseiten auf die Swap-Partition — das ist langsam, aber besser als nichts.
Swap-Thrashing passiert, wenn das System ständig Daten zwischen RAM und Swap hin- und herschieben muss, weil beides zu knapp ist. Das Ergebnis: die Festplatte läuft auf 100%, jede Speicheranforderung blockiert, und der Load Average schießt durch die Decke — obwohl die CPU kaum Rechenarbeit macht. Es geht alles im I/O-Wait verloren.
📖 I/O-Wait
I/O-Wait (kurz: wa in top) ist der Anteil der CPU-Zeit, in der ein Prozessor nichts tut und nur wartet, bis ein Festplatten- oder Speicherzugriff abgeschlossen ist. Bei extremem Swap-Thrashing steht I/O-Wait bei 90–99%. Die CPU ist faktisch beschäftigt — mit Warten. Das erklärt, warum ein Load von 100 bei einem scheinbar „leerlaufenden“ Server möglich ist.
📖 OOM-Killer
Der OOM-Killer (Out-Of-Memory Killer) ist eine Notfallmaßnahme des Linux-Kernels. Wenn RAM und Swap vollständig erschöpft sind und keine Speicheranforderung mehr erfüllt werden kann, greift der Kernel ein: Er wählt nach einem Punktesystem den „teuersten“ Prozess aus — meistens den, der am meisten Speicher belegt — und erschießt ihn mit SIGKILL. Kein sauberes Herunterfahren, kein Speichern: sofort tot.
Der OOM-Killer ist das letzte Mittel. Wenn er feuert, ist das System bereits in einem kritischen Zustand. Im Kernel-Log erscheint er als:
kernel: Out of memory: Killed process 12345 (java)
📖 Varnish
Varnish ist ein HTTP-Cache, der vor dem eigentlichen Webserver sitzt. Häufig angefragte Seiten werden im RAM gespeichert — kommt die gleiche Anfrage nochmal, antwortet Varnish direkt aus dem Cache, ohne den Webserver oder die Datenbank zu bemühen. Das macht Websites deutlich schneller.
Das Problem: Varnish reserviert eigenen RAM für seinen Cache (-s malloc,256m = 256 MB). Wenn der Gesamtspeicher des Systems erschöpft ist, kann Varnish nichts mehr allozieren — und crasht. Der Entwickler hatte bei dieser Situation sogar Humor hinterlassen: Die Panic-Meldung lautet wortwörtlich „It’s time for the big quit“.
📖 JVM-Heap & -Xmx
Die JVM (Java Virtual Machine) ist die Laufzeitumgebung für Java-Programme. Sie verwaltet Speicher intern über einen sogenannten Heap — einen reservierten Speicherbereich für alle Objekte des Programms.
-Xmx1024M sagt der JVM: „Dein Heap darf maximal 1 GB groß werden.“ Das klingt wie eine harte Begrenzung — ist es aber nicht. Zusätzlich zum Heap braucht die JVM:
- Metaspace (Klassen-Metadaten): ~200–400 MB
- JIT-Compiler-Code-Cache (kompilierter nativer Code): ~50–100 MB
- Thread-Stacks: pro Thread ca. 512 KB–1 MB — ein Minecraft-Server hat 50–150 Threads
- Native Bibliotheken und OS-Overhead
Das Ergebnis: Ein Prozess mit -Xmx1024M kann locker 3–4 GB Virtual Memory belegen.
📖 LinuxGSM & PaperMC
LinuxGSM (Linux Game Server Manager) ist ein Shell-Script-Framework, das das Verwalten von Spielservern automatisiert: Starten, Stoppen, Updaten, Monitoren, Backups. Es bringt eigene Cron-Jobs mit, die z.B. alle 5 Minuten prüfen, ob der Server noch läuft, und ihn bei Bedarf automatisch neu starten.
PaperMC ist eine optimierte Minecraft-Server-Implementierung, kompatibel mit Bukkit/Spigot-Plugins, aber mit besserer Performance und häufigen Build-Updates.
Erste Eindrücke: Was läuft überhaupt noch?
SSH-Login. Der Server antwortet — träge, aber lebendig. free -h kommt durch:
Mem: 1.8Gi 1.7Gi 50Mi
Swap: 2.0Gi 2.0Gi 0Ki
Swap: 0 KB frei. RAM: fast leer. Das ist keine Performance-Schwäche — das ist eine Speicherkatastrophe. Ein System mit 0 Bytes freiem Swap ist am Ende. Jede neue Speicheranforderung schlägt fehl oder blockiert, bis der OOM-Killer eingreift. Genau das erklärt den Load von 100+: kein CPU-Problem, reines Swap-Thrashing mit massivem I/O-Wait.
Spurensuche im Journal
Kein Raten. Direkt die Beweise holen:
journalctl --since '23:40' --until '00:20' -p err..emerg --no-pager
Der erste Treffer springt sofort ins Auge:
00:03:25 kernel: Out of memory: Killed process 86688 (java)
total-vm:3882744kB, anon-rss:164148kB, UID:1012
Free swap = 0kB — Total swap = 2097148kB
Der OOM-Killer hat um 00:03:25 Uhr einen Java-Prozess erschossen. 3,8 GB Virtual Memory, alle 2 GB Swap verbraucht.
Direkt danach, in der gleichen Sekunde, die Kettenreaktion:
00:03:25 varnishd: Child (2127550) Panic at: [...]
"It's time for the big quit"
00:03:25 systemd-journald: Watchdog timeout (limit 3min)!
00:03:24 systemd: cron.service: A process of this unit
has been killed by the OOM killer.
Varnish kann keinen Speicher mehr allozieren → Varnish-Kind-Prozess panikt. Dann crasht systemd-journald selbst, weil es keine Log-Einträge mehr schreiben kann. Das gesamte System kollabiert in einer Kaskade.
Den Täter identifizieren: UID 1012
Die entscheidende Zeile aus dem Kernel-Log:
UID:1012 [...] task=java, pid=86688
task_memcg=/system.slice/cron.service
Ein Java-Prozess, gestartet aus dem cron-Dienst. UID 1012 — wer ist das?
getent passwd 1012
pmcserver:x:1012:1012:,,,:/home/pmcserver:/bin/bash
pmcserver. Ein Minecraft-Server-User. Auf einem Web-VPS mit 1,8 GB RAM.
Das Geständnis: Was wirklich passierte
Blick in die Cron-Logs vor dem Absturz:
23:40:01 CRON: (pmcserver) CMD (/home/pmcserver/pmcserver monitor)
23:55:01 CRON: (pmcserver) CMD (/home/pmcserver/pmcserver monitor)
Und der Crontab des pmcserver-Users:
@reboot /home/pmcserver/pmcserver start
*/5 * * * * /home/pmcserver/pmcserver monitor
*/30 * * * * /home/pmcserver/pmcserver update ← Hauptverdächtiger
0 0 * * 0 /home/pmcserver/pmcserver update-lgsm
*/30 * * * * — Update alle 30 Minuten. Das bedeutet: Um 23:30 Uhr und 00:00 Uhr startet LinuxGSM einen zusätzlichen Java-Prozess, um auf neue PaperMC-Versionen zu prüfen. Der bereits laufende Minecraft-Server plus der Update-Prozess zusammen haben RAM und Swap aufgefressen.
Das Perverse: Der */5 * * * * monitor-Cron hat nach dem OOM-Kill den Minecraft-Server automatisch neu gestartet. Um 00:05 Uhr lief Java wieder und fraß bereits wieder 810 MB RAM.
Die vollständige Tatkette
| Zeit | Ereignis |
|---|---|
| 23:30 | pmcserver update-Cron startet — zweiter Java-Prozess spawnt |
| 23:30–23:45 | RAM erschöpft, System beginnt zu swappen, Load steigt |
| 23:45 | Monitoring-Alert: Load > Schwellwert |
| 23:45–00:03 | Swap-Thrashing, Load > 100 (reines I/O-Wait) |
| 00:00 | Nächster Update-Cron feuert — finaler Sargnagel |
| 00:03:25 | OOM-Killer erschießt Java |
| 00:03:25 | Varnish panikt, journald crasht, cron bricht ab |
| 00:05 | pmcserver monitor startet Java automatisch neu |
Die Lösung: Sauber migrieren
1. Server graceful stoppen
su - pmcserver -c '/home/pmcserver/pmcserver stop'
LinuxGSM schickt zuerst den /stop-Befehl an den Minecraft-Server, wartet auf sauberes Herunterfahren — alle Chunks werden korrekt auf Disk geschrieben — dann beendet es den Prozess. Kein kill -9, kein Datenverlust.
2. Crontab entfernen
crontab -u pmcserver -r
Ohne das würde @reboot bei jedem Serverneustart alles wieder starten, und */5 monitor würde den Prozess nach jedem Kill innerhalb von 5 Minuten erneut starten. Der Kreislauf würde sich wiederholen.
3. Worlds mit Versions-Tag archivieren
VER=$(cat /home/pmcserver/serverfiles/build.txt)
DATE=$(date +%Y-%m-%d)
tar -czf /tmp/mc_worlds_paper-${VER}_${DATE}.tar.gz \
-C /home/pmcserver/serverfiles \
world-two world-two_nether world-two_the_end \
server.properties ops.json whitelist.json \
banned-players.json banned-ips.json plugins
Ergebnis: mc_worlds_paper-1.21.10-130_2026-06-18.tar.gz (91 MB). Die Build-Nummer im Dateinamen ist entscheidend: In einem Jahr steht sofort fest, mit welcher PaperMC-Version die Welt zuletzt lief — und ob ein direktes Laden auf einer neueren Version funktioniert oder Migrations-Schritte nötig sind.
4. Transfer auf den dedizierten Minecraft-Container
Da der Ziel-Container im internen Netz liegt und den VPS nicht direkt erreichen kann, läuft der Transfer über die lokale Maschine als Relay:
# Lokal herunterladen
scp root@vps-web:/tmp/mc_worlds_paper-1.21.10-130_2026-06-18.tar.gz /tmp/
# Auf Minecraft-Container hochladen (via ProxyJump durch den PVE-Host)
scp -o ProxyJump=root@pve-host /tmp/mc_worlds_paper-1.21.10-130_2026-06-18.tar.gz \
root@mc-container:/home/pmcserver/
5. Sicher entpacken — ohne das bestehende Save zu überschreiben
cd /home/pmcserver/serverfiles
mkdir worlds_from_vps_paper-1.21.10-130_2026-06-18
tar -xzf /home/pmcserver/mc_worlds_paper-1.21.10-130_2026-06-18.tar.gz \
-C worlds_from_vps_paper-1.21.10-130_2026-06-18/
So kann in Ruhe entschieden werden — tauschen, mergen oder als Backup behalten — ohne versehentlich Daten zu verlieren.
Ergebnis
| Vorher | Nachher | |
|---|---|---|
| RAM frei | 50 MB | 463 MB |
| Swap frei | 0 MB | 1.200 MB |
| Load Average | > 100 | 1.3 |
| Java | 810 MB RSS, läuft | nicht mehr auf diesem Server |
Lessons Learned
Load Average ≠ CPU-Last. Ein Load von 100 bedeutet nicht, dass die CPU brennt. Es bedeutet meistens Swap-Thrashing oder blockierte I/O-Operationen. Erster Blick immer: free -h und iostat -x 1.
Java braucht mehr RAM als sein Heap. -Xmx1024M ist nicht der Gesamtbedarf des Prozesses — es ist nur das Heap-Limit. Metaspace, JIT, Threads und nativer Code kommen obendrauf. Auf kleinen VPS mit < 4 GB RAM haben Java-Anwendungen mit großem Heap nichts zu suchen.
Cron-basiertes Auto-Restart ist eine Falle. Ein Monitor-Cron, der nach einem Crash neu startet, ist praktisch — aber er kann einen OOM-Kreislauf erzeugen: crashen → neu starten → wachsen → crashen → … Die richtige Lösung ist ein systemd-Unit mit MemoryMax= und Restart=on-failure, kombiniert mit einem vernünftigen RAM-Budget.
Archiviere mit Versions-Tag. world.tar.gz ist nutzlos. mc_worlds_paper-1.21.10-130_2026-06-18.tar.gz sagt dir alles, was du in einem Jahr noch wissen musst.
Dienste gehören auf passende Hardware. Ein Minecraft-Server auf einem Web-VPS, der bereits nginx, MariaDB und Varnish betreibt, ist eine tickende Zeitbombe. Dedizierte Ressourcen oder zumindest ein systemd MemoryMax= — eine der beiden Optionen ist Pflicht.
Gesamte Diagnose- und Migrationszeit: ~25 Minuten.
🔗 Kurzlink: https://nga.li/9YXv
Kommentar schreiben