Ein Dienst ist weg, das Protokoll des Dienstes endet mitten im Satz, und niemand hat ihn beendet. Auf einem kleinen VPS ist die Erklärung fast immer dieselbe: Der Kernel hat entschieden, dass jemand sterben muss, und hat sich das Opfer ausgesucht. Dieser Beitrag zeigt, wie du das nachweist, wie du die Auswahl beeinflusst und welche Einstellungen auf 1-4 GB RAM tatsächlich etwas bewirken.
- Der OOM-Killer ist kein Fehler, sondern die letzte Instanz. Er greift, wenn keine Seite mehr frei zu machen ist und er trifft selten den Schuldigen, sondern den Größten.
- „Frei“ in
free -hist die falsche Spalte. Die interessante heißtavailable; alles andere ist Cache, den der Kernel jederzeit hergibt. - Swap verhindert den Abschuss nicht, er verschiebt ihn und kauft dir damit die Minuten, in denen ein Alarm ankommen kann.
- Wer entscheidet, wer stirbt, ist eine Einstellung:
OOMScoreAdjustin der systemd-Unit, nicht Zufall.
Zuerst nachweisen, dann reparieren#
Bevor irgendetwas eingestellt wird, muss feststehen, dass es überhaupt der OOM-Killer war. Der Nachweis steht im Kernel-Protokoll, nicht im Protokoll des Dienstes:
journalctl -k --since "-7d" | grep -iE "out of memory|oom-kill|killed process"
Eine typische Zeile sieht so aus:
kernel: Out of memory: Killed process 1417 (mariadbd) total-vm:1284932kB,
anon-rss:412208kB, file-rss:0kB, shmem-rss:0kB, UID:107 pgtables:1204kB oom_score_adj:0
Drei Angaben daraus sind brauchbar: welcher Prozess, wie viel Arbeitsspeicher er wirklich belegt hatte (anon-rss, nicht total-vm) und welchen Score er trug (oom_score_adj). Steht dort nichts, war es kein OOM, dann sind Absturz, Signal oder ein Neustart durch den Paketmanager die wahrscheinlicheren Erklärungen.
Bei systemd-Diensten steht die Quittung zusätzlich im Status:
systemctl status mariadb | grep -E "Main PID|Active"
# "Active: failed (Result: oom-kill)" ist die eindeutige Aussage.
total-vm ist nicht der belegte Speicher. Es ist der reservierte Adressraum, und der ist bei Datenbanken und der JVM regelmäßig ein Vielfaches des tatsächlichen Bedarfs. Wer danach dimensioniert, kauft eine zu große Maschine und hat das Problem trotzdem noch.
Warum „free“ beruhigt und trotzdem nichts sagt#
Auf einem Linux-Server ist beinahe der gesamte Speicher belegt, das ist der Normalzustand, kein Symptom. Was nicht von Prozessen gebraucht wird, benutzt der Kernel als Cache für Dateien, und den gibt er sofort zurück, wenn jemand ihn braucht.
free -h
# total used free shared buff/cache available
# Mem: 1.9Gi 1.1Gi 102Mi 18Mi 780Mi 620Mi
Die einzige Spalte, die eine Aussage trägt, ist available: so viel könnte ein neuer Prozess bekommen, ohne dass geswappt werden muss. free mit 102 MiB ist hier vollkommen unauffällig.
Für den Verlauf statt der Momentaufnahme:
# Sekundengenau, während der Dienst gerade Ärger macht
vmstat 1 30
# Interessant: si/so (Swap ein/aus). Dauerhaft > 0 heißt: die Maschine rudert.
Wer wissen will, wer den Speicher hält, fragt nicht top, sondern die Kontrollgruppen, dort steht es je Dienst statt je Prozess:
systemd-cgtop -m --order=memory
Der Weg dorthin ist der gleiche wie beim Messen von CPU und Platte: erst messen, dann urteilen. Wie das für die übrigen Werte geht, steht in VPS-Leistung selbst messen.
Swap: nicht mehr Speicher, sondern mehr Zeit#
Swap macht eine 2-GB-Maschine nicht zu einer 4-GB-Maschine. Er verschiebt selten benutzte Seiten auf die Platte und verschafft dem System damit Luft und dir die Minuten, in denen ein Alarm ankommt, bevor der Kernel jemanden abschießt.
Auf einem VPS ohne Swap-Partition ist eine Datei der richtige Weg:
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
Zwei Einstellungen entscheiden über das Verhalten, und beide werden regelmäßig falsch herum gedreht, die maßgebliche Beschreibung steht in der Kernel-Dokumentation zu vm-Parametern:
# Wie bereitwillig wird ausgelagert? 60 ist die Voreinstellung.
sysctl vm.swappiness=10 # Server mit Datenbank: früh, aber zurückhaltend
# Wie stark wird der Datei-Cache gegenüber Prozessen bevorzugt?
sysctl vm.vfs_cache_pressure=50
Dauerhaft gehört das nach /etc/sysctl.d/99-speicher.conf, nicht in ein Startskript.
Ein Dienst mit einem Speicherleck belegt mit Swap genau dasselbe, nur langsamer und die Maschine wird dabei zäh statt tot. Swap ist ein Puffer, kein Ersatz für eine Obergrenze. Die Obergrenze steht im nächsten Abschnitt.
Wer stirbt, ist eine Entscheidung, keine Zufallsauswahl#
Der Kernel wählt sein Opfer nach einem Punktestand, der grob am belegten Arbeitsspeicher hängt. Auf einem kleinen Server heißt das fast immer: die Datenbank stirbt, nicht der Angreifer-Prozess, der sie ausgelöst hat. Das ist selten die Reihenfolge, die man will.
Systemd kennt dafür zwei Schrauben, die zusammengehören (systemd.exec für OOMScoreAdjust, systemd.resource-control für die Speichergrenzen):
# /etc/systemd/system/mariadb.service.d/speicher.conf
[Service]
# Negativ = wird später erschossen. Bereich -1000 bis 1000.
OOMScoreAdjust=-500
# /etc/systemd/system/importer.service.d/speicher.conf
[Service]
# Der Stapelverarbeiter darf zuerst gehen — und bekommt eine harte Grenze.
OOMScoreAdjust=500
MemoryMax=512M
MemoryHigh=384M
MemoryHigh bremst den Dienst, sobald er die Marke überschreitet; MemoryMax ist die Wand, an der er scheitert. Der Unterschied ist wichtig: MemoryHigh erzeugt Druck, MemoryMax erzeugt einen Toten, aber einen erwarteten, in einer Kontrollgruppe, ohne den Rest der Maschine mitzureißen.
systemctl daemon-reload && systemctl restart importer
systemctl show importer -p MemoryMax -p OOMScoreAdjust
Für regelmäßig laufende Stapelarbeiten ist die Kombination aus Grenze und Zeitgeber die saubere Form, warum ein Timer dem Cronjob dabei überlegen ist, steht in systemd-Timer statt Cronjob.
Die vier Stellen, an denen kleine Maschinen wirklich verlieren#
Auf 1-4 GB entstehen die Engpässe erfahrungsgemäß nicht dort, wo man sie sucht:
- Datenbank-Puffer stehen auf Voreinstellungen für große Maschinen. Bei MariaDB ist es die
innodb_buffer_pool_size, bei PostgreSQLshared_buffersund die Zahl paralleler Verbindungen. Details je System in MariaDB auf kleinen Maschinen und PostgreSQL auf dem VPS. - Jeder Container bringt seine eigene Laufzeit mit. Fünf Compose-Dienste mit je einer Node- oder PHP-Laufzeit sind schnell ein Gigabyte, bevor die erste Anfrage kommt, siehe Docker im echten Betrieb.
- Protokolle im Arbeitsspeicher.
journalddarf per Voreinstellung einen Anteil des RAM belegen.SystemMaxUseundRuntimeMaxUsein/etc/systemd/journald.confsetzen dem eine Zahl entgegen. - Der Backup-Lauf zur vollen Stunde. Kompression und Prüfsummen brauchen Speicher; wenn sie gleichzeitig mit dem Tagesgeschäft laufen, kippt die Maschine genau dann. Ein Zeitgeber mit
MemoryMaxlöst das, siehe Restic: verschlüsselte Sicherungen.
Alarm vor dem Abschuss#
Ein OOM, den man erst am nächsten Tag im Protokoll findet, ist ein Ausfall. Derselbe OOM mit einer Nachricht zehn Minuten vorher ist ein Wartungsfenster. Zwei Bedingungen reichen dafür:
availableunter 15 % über mehr als fünf Minuten, das ist der Vorlauf.- Ein OOM-Ereignis im Kernel-Protokoll, das ist die Quittung, und sie gehört in einen eigenen, lauteren Kanal.
Wie ein Alarmweg aussieht, der nachts auch wirklich ankommt, steht in Monitoring aufsetzen.
Häufige Fragen#
Woran erkenne ich sicher, dass der OOM-Killer zugeschlagen hat?#
An einer Zeile im Kernel-Protokoll (journalctl -k | grep -i "killed process") oder an Result: oom-kill im systemctl status des Dienstes. Ohne einen dieser beiden Belege war es kein OOM, egal wie sehr es danach aussieht.
Wie viel Swap ist auf einem kleinen VPS sinnvoll?#
Als Ausgangspunkt: so viel wie Arbeitsspeicher, höchstens aber 2-4 GB. Mehr hilft nicht, weil eine Maschine, die dauerhaft mehrere Gigabyte auslagert, ohnehin unbenutzbar langsam ist. Entscheidend ist nicht die Größe, sondern dass Swap überhaupt existiert und dass si/so in vmstat im Normalbetrieb bei null stehen.
Ist vm.swappiness=0 nicht die sicherste Einstellung?#
Nein. 0 schaltet das Auslagern nicht ab, sondern erlaubt es erst im letzten Moment, genau dann, wenn ohnehin schon alles zäh ist. Auf Servern mit Datenbank ist ein kleiner Wert wie 10 die üblichere Wahl: früh genug, um Luft zu haben, zurückhaltend genug, um nicht ständig zu rudern.
Verhindert MemoryMax Abstürze?#
Es verhindert, dass ein einzelner Dienst die ganze Maschine mitreißt. Der begrenzte Dienst selbst scheitert weiterhin, wenn er über seine Grenze will, nur eben vorhersehbar, in seiner eigenen Kontrollgruppe, und der Rest läuft weiter.
Was hängen bleiben sollte#
- Erst den Beleg suchen (
journalctl -k), dann einstellen. Ohne Beleg repariert man Vermutungen. availableist die Spalte,freeist Dekoration.- Swap kauft Zeit, keine Kapazität und Zeit ist genau das, was ein Alarm braucht.
- Wer stirbt, entscheidest du mit
OOMScoreAdjustundMemoryMax, nicht der Zufall.
- Kernel-Dokumentation, Sysctl-Parameter unter
vm,swappiness,vfs_cache_pressure, Verhalten bei Speicherdruck - Kernel-Dokumentation, Concepts overview der Speicherverwaltung, warum Cache als „belegt“ zählt
- systemd.resource-control(5) und systemd.exec(5),
MemoryHigh,MemoryMax,OOMScoreAdjust
Abgerufen am 23.08.2026. Alle Zahlenbeispiele in diesem Beitrag stammen aus einer Testmaschine und sind als Beispiel gekennzeichnet, die Werte für deine Maschine erzeugst du mit den Befehlen oben.
Der nächste sinnvolle Schritt hängt davon ab, was du gefunden hast: Bei Ausreißern im Betrieb lohnt Monitoring aufsetzen, bei dauerhaft knappem Speicher zuerst die Datenbank in MariaDB auf kleinen Maschinen.
Geschrieben aus dem laufenden Betrieb: MRMedia betreibt eigene Proxmox- Hosts, einen Backup-Server und Kundendienste in der EU. Fehler in einer Anleitung sind Fehler im eigenen Betrieb. Korrekturen bitte über Discord.