Сервер работал, процесс исчез. Без segfault, без явной ошибки в логах приложения. Если в системных журналах есть строка Out of memory: Kill process — это OOM Killer. Ядро Linux решило что памяти не хватает и выбрало жертву по своему алгоритму. Разбираем как найти что именно убили, почему выбрали именно этот процесс и как это предотвратить.
Шаг 1: подтвердить что виновник — OOM Killer
Смотреть в журнал ядра:
sudo journalctl -k | grep -i "oom\|kill" | tail -30
Или через dmesg с временными метками:
dmesg -T | grep -i "oom\|killed process" | tail -30
Типичный вывод:
[Mar 24 03:17:42] Out of memory: Kill process 14821 (php-fpm) score 847 or sacrifice child
[Mar 24 03:17:42] Killed process 14821 (php-fpm) total-vm:2048000kB, anon-rss:1843200kB, file-rss:4096kB, shmem-rss:0kB
Здесь: имя процесса php-fpm, PID 14821, score 847 — чем выше, тем вероятнее убийство, и размер занятой памяти в момент убийства.
Посмотреть все OOM-события за последние сутки:
sudo journalctl -k --since "24 hours ago" | grep "Out of memory"
Шаг 2: понять почему убили именно этот процесс
OOM Killer не убивает случайно. Каждый процесс получает oom_score от 0 до 1000 — чем выше, тем привлекательнее жертва. Алгоритм учитывает: размер занятой памяти, время жизни процесса (новые менее ценны), запущен ли как root, и oom_score_adj — ручная поправка.
Посмотреть текущий score любого процесса по PID:
cat /proc/$(pgrep nginx)/oom_score
Посмотреть поправку — oom_score_adj:
cat /proc/$(pgrep nginx)/oom_score_adj
Диапазон oom_score_adj: от -1000 (никогда не убивать) до +1000 (убить первым).
Топ процессов по oom_score прямо сейчас:
ps aux --sort=-%mem | head -10 | awk '{print $2}' | xargs -I{} sh -c 'echo -n "{} $(cat /proc/{}/comm 2>/dev/null): "; cat /proc/{}/oom_score 2>/dev/null' | sort -t: -k2 -rn | head -10
Шаг 3: посмотреть сколько памяти было в момент убийства
OOM Killer перед убийством печатает в dmesg полный снимок состояния памяти. Найти его:
dmesg -T | grep -A 30 "Out of memory" | head -50
В выводе будет таблица всех процессов с их RSS в момент события. Это позволяет понять кто реально съел всю память — не всегда это тот кого убили.
Как защитить конкретный процесс от убийства
Выставить oom_score_adj = -1000 — ядро никогда не убьёт этот процесс:
echo -1000 | sudo tee /proc/$(pgrep sshd)/oom_score_adj
Для systemd-сервисов — добавить в unit-файл:
sudo systemctl edit nginx
[Service]
OOMScoreAdjust=-900
Применить:
sudo systemctl daemon-reload
sudo systemctl restart nginx
Значение -900 — очень маловероятно убьют. -1000 — гарантированно не убьют (используйте только для критических сервисов вроде sshd).
Как сделать процесс более привлекательной жертвой
Например, фоновый воркер не критичен — пусть OOM Killer возьмёт его первым:
echo 500 | sudo tee /proc/$(pgrep worker)/oom_score_adj
Или в systemd unit:
[Service]
OOMScoreAdjust=500
Системные настройки: vm.overcommit_memory
По умолчанию Linux разрешает overcommit — процессы могут зарезервировать больше памяти чем физически есть, в расчёте что не используют всё сразу. Когда использование превышает реальный объём — приходит OOM Killer.
Посмотреть текущий режим:
cat /proc/sys/vm/overcommit_memory
Три режима:
0— эвристический overcommit (по умолчанию)1— разрешить любой overcommit без ограничений2— запретить overcommit; процессы получат ошибку при выделении если памяти нет
Режим 2 самый предсказуемый для продакшн-серверов. Процесс упадёт с ENOMEM при попытке выделить память а не будет убит внезапно.
Установить режим 2:
sudo sysctl -w vm.overcommit_memory=2
Сделать постоянным:
echo "vm.overcommit_memory=2" | sudo tee -a /etc/sysctl.d/99-memory.conf
sudo sysctl -p /etc/sysctl.d/99-memory.conf
При режиме 2 также настроить vm.overcommit_ratio — какой процент RAM+swap разрешён:
echo "vm.overcommit_ratio=80" | sudo tee -a /etc/sysctl.d/99-memory.conf
vm.swappiness: замедлить OOM через swap
Если swap есть но не используется — ядро держит данные в RAM и вызывает OOM раньше времени. Снизить порог переключения на swap:
cat /proc/sys/vm/swappiness
По умолчанию 60. На VPS где swap медленный — лучше 10–20:
sudo sysctl -w vm.swappiness=10
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.d/99-memory.conf
Ограничить память через cgroup (не дать процессу убить сервер)
Вместо того чтобы ждать OOM — ограничить сколько памяти может занять конкретный сервис. Тогда OOM Killer убьёт только воркеры этого сервиса, не трогая остальные.
Для systemd-сервиса:
sudo systemctl edit php8.1-fpm
[Service]
MemoryMax=512M
MemorySwapMax=0
MemoryMax — жёсткий лимит RAM. MemorySwapMax=0 — запретить использовать swap. При превышении лимита cgroup триггерит локальный OOM внутри группы и убивает только процессы этого сервиса.
Посмотреть текущее потребление памяти по сервисам:
systemd-cgtop -m
Мониторинг: получить алерт до того как рванёт
Скрипт который отправляет предупреждение когда доступная память падает ниже порога:
#!/bin/bash
THRESHOLD=10
AVAILABLE=$(free | awk '/^Mem:/ {printf "%.0f", $7/$2*100}')
if [ "$AVAILABLE" -lt "$THRESHOLD" ]; then
echo "WARNING: only ${AVAILABLE}% RAM available on $(hostname)" | \
mail -s "Low memory alert" admin@example.com
fi
Добавить в cron каждые 5 минут:
*/5 * * * * /usr/local/bin/memory-check.sh
Шпаргалка
| Задача | Команда |
|---|---|
| Найти OOM-события | sudo journalctl -k | grep "Out of memory" |
| OOM через dmesg | dmesg -T | grep -i "oom|killed process" |
| oom_score процесса | cat /proc/PID/oom_score |
| Защитить процесс от убийства | echo -1000 | sudo tee /proc/PID/oom_score_adj |
| Защита через systemd | OOMScoreAdjust=-900 в unit-файле |
| Режим overcommit | cat /proc/sys/vm/overcommit_memory |
| Запретить overcommit | sysctl -w vm.overcommit_memory=2 |
| Ограничить RAM сервиса | MemoryMax=512M в systemd unit |
| Мониторинг по сервисам | systemd-cgtop -m |
| Текущий swappiness | cat /proc/sys/vm/swappiness |