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

ZeitEreignis
23:30pmcserver update-Cron startet — zweiter Java-Prozess spawnt
23:30–23:45RAM erschöpft, System beginnt zu swappen, Load steigt
23:45Monitoring-Alert: Load > Schwellwert
23:45–00:03Swap-Thrashing, Load > 100 (reines I/O-Wait)
00:00Nächster Update-Cron feuert — finaler Sargnagel
00:03:25OOM-Killer erschießt Java
00:03:25Varnish panikt, journald crasht, cron bricht ab
00:05pmcserver 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

VorherNachher
RAM frei50 MB463 MB
Swap frei0 MB1.200 MB
Load Average> 1001.3
Java810 MB RSS, läuftnicht 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