Zum Inhalt springen
myvpsguide

Speicher knapp: OOM-Killer verstehen, Swap richtig setzen

Warum der Kernel Dienste abschießt statt zu warten, wie du das Opfer vorher bestimmst und welche vier Einstellungen auf kleinen Maschinen wirklich etwas ändern.

veröffentlicht 23.08.2026
7 min lesezeit
fortgeschritten

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.

Kurz gefasst
  • 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 -h ist die falsche Spalte. Die interessante heißt available; 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: OOMScoreAdjust in 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.
Der häufigste Irrtum

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.

Was Swap nicht repariert

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:

  1. Datenbank-Puffer stehen auf Voreinstellungen für große Maschinen. Bei MariaDB ist es die innodb_buffer_pool_size, bei PostgreSQL shared_buffers und die Zahl paralleler Verbindungen. Details je System in MariaDB auf kleinen Maschinen und PostgreSQL auf dem VPS.
  2. 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.
  3. Protokolle im Arbeitsspeicher. journald darf per Voreinstellung einen Anteil des RAM belegen. SystemMaxUse und RuntimeMaxUse in /etc/systemd/journald.conf setzen dem eine Zahl entgegen.
  4. 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 MemoryMax lö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:

  • available unter 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.
  • available ist die Spalte, free ist Dekoration.
  • Swap kauft Zeit, keine Kapazität und Zeit ist genau das, was ein Alarm braucht.
  • Wer stirbt, entscheidest du mit OOMScoreAdjust und MemoryMax, nicht der Zufall.
Primärquellen

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.

MRMedia

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.