Настройка swap: как предотвратить аварии из-за нехватки памяти
Служба исчезла, отчёта об аварии нет, лог обрывается на середине строки: как доказать OOM-kill, аккуратно создать файл подкачки и понять, когда swap лишь откладывает проблему.
Служба исчезла. Ни отчёта об аварии, ни стека вызовов, лог обрывается на середине строки. MariaDB не отвечает, nginx отдаёт 502, Minecraft-сервер офлайн, а в самом приложении ничего подозрительного нет. Почти всегда это один и тот же сценарий: у ядра закончилась свободная память, и оно завершило процесс, чтобы система осталась живой. В этой статье показано, как доказать это без всяких сомнений, как аккуратно создать файл подкачки и прописать его на постоянной основе, а также в каких случаях swap лишь отодвигает проблему на несколько минут.
Собираем доказательства: dmesg и journalctl
Прежде чем что-либо настраивать, нужны доказательства. При каждом OOM-kill (out of memory) ядро записывает в кольцевой буфер подробный блок:
dmesg -T | grep -iE 'out of memory|oom-kill'
Если ни одной строки не возвращается, а команда завершается с кодом 1, значит с момента последней загрузки OOM на уровне ядра не было. Код возврата здесь не ошибка, а обычный ответ grep, когда совпадений нет. Если же вывод есть, на всех четырёх рассматриваемых системах он выглядит так:
mariadbd invoked oom-killer: gfp_mask=0x140cca(GFP_HIGHUSER_MOVABLE|__GFP_COMP), order=0, oom_score_adj=0
oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/mariadb.service,task=mariadbd,pid=1043,uid=107
Out of memory: Killed process 1043 (mariadbd) total-vm:2894760kB, anon-rss:1583204kB, file-rss:0kB, shmem-rss:0kB, UID:107 pgtables:3820kB oom_score_adj:0
Здесь важны три вещи. Во-первых, invoked oom-killer называет процесс, который запросил память, а не обязательно виновника. Во-вторых, завершённый процесс указан в строке Killed process. В-третьих, ориентироваться надо на anon-rss, то есть на реально занятую анонимную память. total-vm — это зарезервированное адресное пространство, у Java или Go оно регулярно превышает реальное потребление в разы и ни о чём не говорит.
Если dmesg запускается без root, то на Debian 12, Debian 13, Ubuntu 22.04 и Ubuntu 24.04 он отвечает dmesg: read kernel buffer failed: Operation not permitted, потому что kernel.dmesg_restrict везде выставлен в 1. Значит, работать нужно через sudo или под root. Если сообщение остаётся и под root, вы находитесь в LXC- или OpenVZ-контейнере, у которого нет доступа к кольцевому буферу хост-системы. Тогда остаётся только второй путь, через journalctl -k.
После перезагрузки кольцевой буфер пуст. Поэтому и нужен второй путь, через журнал, и вот здесь скрыта ловушка, мимо которой проходит большинство руководств:
journalctl -k --grep "Out of memory"
Ключ -k неявно включает -b, то есть показывает исключительно текущую загрузку. Если машина после инцидента была перезагружена, эта команда не выдаст ничего, хотя событие в журнале записано. Для предыдущей загрузки или для интервала времени:
journalctl -k -b -1 --grep "Out of memory"
journalctl --since "7 days ago" --grep "Out of memory|oom-kill"
Если первая из двух команд отвечает No journal boot entry found for the specified boot (-1), то в журнале просто нет более ранней загрузки. Это тоже не ошибка, а обычная ситуация для системы, журнал которой ведётся только с текущего запуска.
Работает всё это лишь при условии, что журнал вообще постоянный. Проверьте:
ls -d /var/log/journal
На Debian и Ubuntu этот каталог присутствует с завода, поэтому проверка почти всегда что-то находит, а следующая команда mkdir просто ничего не делает и ничего не ломает. Если каталога в виде исключения всё же нет, журнал лежит только в /run и теряется при каждой перезагрузке. Наверстать это можно двумя командами:
mkdir -p /var/log/journal
systemctl restart systemd-journald
OOM ядра или лимит systemd? Две разные причины
Именно это различие решает, даст ли swap хоть какой-то эффект. Обратите внимание на поле constraint в сообщении ядра.
CONSTRAINT_NONE означает: память закончилась у всей системы. Здесь swap помогает.
CONSTRAINT_MEMCG означает: лимит превысила лишь одна control-group, у остальной системы запас был приличный. Здесь swap не поможет, здесь надо поднимать лимит или учить приложение экономить. Такие завершения видны и по самой службе:
systemctl status mariadb
Если там стоит Main process exited, code=killed, status=9/KILL, а ниже Failed with result 'oom-kill', значит это был OOM-kill. Был ли при этом задействован лимит cgroup, покажет файл со счётчиками. Он требует cgroup v2, который на всех четырёх дистрибутивах используется по умолчанию; в более старом cgroup v1 такого пути нет:
cat /sys/fs/cgroup/system.slice/mariadb.service/memory.events
systemctl show mariadb -p MemoryMax -p MemoryHigh
Значение больше нуля у oom_kill вместе с заданным MemoryMax и есть доказательство локального лимита.
Есть и третий кандидат, о котором часто забывают, потому что он вообще ничего не пишет в dmesg: systemd-oomd. Эта служба работает в пространстве пользователя, анализирует показатель нагрузки PSI и завершает целые control-group ещё до того, как вмешается ядро. В журнале её сообщение выглядит примерно так: Killed /system.slice/... due to memory pressure for /system.slice being 60.00% > 50.00% for > 20s with reclaim activity. Проверьте, запущена ли она:
systemctl is-active systemd-oomd
В Ubuntu systemd-oomd начиная с 22.04 установлен и активен с завода, в Debian он в стандартную поставку не входит. Поэтому inactive на Debian-сервере ожидаем и признаком неисправности не является.
Важно для дальнейшего: systemd-oomd может сработать и потому, что заполняется swap. То есть при активном oomd больший объём подкачки способен приблизить аварийные завершения, а вовсе не отсрочить их.
Перед настройкой swap: сколько памяти не хватает на самом деле
Две минуты измерений избавляют от неверного решения.
free -h
Интересен исключительно столбец available, а не free. Cache считается доступным и при необходимости освобождается. Тот, кто читает столбец free, любую здоровую систему сочтёт перегруженной.
ps -eo pid,comm,rss,%mem --sort=-rss | head -n 11
systemd-cgtop --order=memory -b -n 1
Первая команда показывает самые крупные отдельные процессы, вторая группирует их по службам. На практике там почти всегда одни и те же три подозреваемых: MariaDB со слишком большим innodb_buffer_pool_size, PHP-FPM со слишком высоким pm.max_children и JVM со слишком щедрым -Xmx.
В качестве отправной точки для размера файла подкачки достаточно этой таблицы. На сервере больше не значит лучше: swap, который действительно используется целиком, делает машину неуправляемой.
| Оперативная память | Разумный размер файла подкачки |
|---|---|
| 1 ГБ | 1-2 ГБ |
| 2 ГБ | 2 ГБ |
| 4-8 ГБ | 2-4 ГБ |
| 16 ГБ и больше | 4 ГБ, редко больше |
Создание файла подкачки
Сначала посмотрите, нет ли swap уже сейчас. Серверы Ubuntu, установленные с ISO-образа, часто уже несут /swap.img, Debian из установщика обычно получает настоящий раздел подкачки. В облачных образах обоих дистрибутивов, как правило, нет ничего.
swapon --show
Если вывод остаётся пустым, swap отсутствует. Следующим шагом проверьте файловую систему, от неё зависит порядок действий:
findmnt -no FSTYPE -T /
На ext4 или xfs можно продолжать сразу. Создавайте файл командой dd, а не fallocate. fallocate работает быстрее, но в зависимости от файловой системы и ядра оставляет в файле незаписанные области, и тогда swapon его отвергает. dd пишет настоящие нули и работает везде:
dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress
chmod 600 /swapfile
Перед следующим шагом стоит выполнить проверку, которую многие пропускают. Если по пути /swapfile уже включён swap, например от прежней попытки или из настроек образа, mkswap откажется работать с сообщением mkswap: error: /swapfile is mounted; will not make swapspace. Значение имеет только одно: числится ли путь активным в /proc/swaps. Поэтому проверьте и при необходимости отключите:
swapon --show
swapoff /swapfile
Если в выводе swapon --show нет строки с /swapfile, команду swapoff можно пропустить. После этого записывается область подкачки:
mkswap /swapfile
mkswap отвечает строкой Setting up swapspace version 1, size = 2 GiB (2147479552 bytes) и новым UUID. Только после этого включаем:
swapon /swapfile
Порядок не обсуждается: chmod перед mkswap, mkswap перед swapon, а возможный swapoff прежде всего остального.
Как убедиться, что всё действительно заработало
То, что swapon отработал без сообщений об ошибке, доказательством ещё не является. Доказательство — это два следующих вывода:
swapon --show
free -h
swapon --show обязан выдать строку с /swapfile, типом file, размером и приоритетом. В free -h строка Swap: должна смениться с 0B на новый размер. Если один из двух выводов остался прежним, swap не активен, что бы ни сообщала команда до этого.
Постоянная запись без риска для загрузки
Команда swapon перезагрузку не переживает. Запись должна попасть в /etc/fstab, и именно там серверы обычно и ломают. Сначала резервная копия:
cp /etc/fstab /etc/fstab.bak
echo '/swapfile none swap sw 0 0' >> /etc/fstab
Обратите внимание на два знака «больше». Одиночный > перезапишет весь файл, и тогда машина уже не загрузится нормально. После этого проверьте синтаксис, прежде чем перезагружаться:
findmnt --verify
Одно предупреждение при этом совершенно нормально и не является поводом убирать строку: [W] non-bind mount source /swapfile is a directory or regular file. Для файла подкачки иначе и быть не может, команда всё равно завершается с кодом возврата 0. Если у файла вдобавок нет корректного заголовка подкачки, добавится [W] cannot detect on-disk filesystem type, и вот тогда вы действительно забыли mkswap.
Если выше вы уже включили swap вручную командой swapon /swapfile, он активен и сейчас. Если нет, swapon -a активирует все записи из fstab без перезагрузки. Но настоящая проверка другая. Из каждой строки fstab systemd создаёт отдельный unit, для /swapfile он называется swapfile.swap. Если этот unit появляется и активен, при следующем запуске swap будет подключён гарантированно:
systemctl daemon-reload
systemctl list-units --type swap
Ожидается строка swapfile.swap loaded active active Swap. Если её нет, строка в fstab неверна, и перезагрузка закончится без swap. Только когда всё сходится, имеет смысл перезагружаться с последующей проверкой через swapon --show.
Правильная настройка swappiness
Параметр ядра vm.swappiness задаёт, насколько охотно анонимные страницы вытесняются в подкачку вместо сброса файлового кэша. На Debian 12, Debian 13, Ubuntu 22.04 и Ubuntu 24.04 значение по умолчанию одинаково и равно 60:
cat /proc/sys/vm/swappiness
Начиная с ядра 5.8 диапазон значений идёт от 0 до 200, и все четыре дистрибутива поставляются с более новыми ядрами. Два распространённых заблуждения. vm.swappiness=0 не отключает подкачку, а лишь запрещает упреждающее вытеснение: ядро всё равно уйдёт в подкачку, прежде чем кого-то завершить. И низкое значение не делает систему быстрее, если памяти попросту не хватает.
Разумные значения: 10-20 на серверах баз данных, 60 на смешанных веб-серверах, 100 и больше при использовании zram. На постоянной основе это прописывается в отдельный файл в /etc/sysctl.d/, а не в /etc/sysctl.conf, который мешается при обновлении пакетов:
echo 'vm.swappiness = 10' > /etc/sysctl.d/99-swappiness.conf
sysctl --system
cat /proc/sys/vm/swappiness
Третья команда служит проверкой результата. sysctl --system читает все каталоги в фиксированном порядке, и уже имеющийся файл с большим номером может перекрыть ваше значение.
Если что-то пошло не так: сообщения об ошибках дословно
swapon: /swapfile: insecure permissions 0644, 0600 suggested. Это лишь предупреждение, swap всё равно работает. Устранить его тем не менее стоит, иначе любой пользователь сможет прочитать содержимое вытесненных процессов: chmod 600 /swapfile.
swapon: /swapfile: swapon failed: Invalid argument Самая частая ошибка. Либо забыли mkswap, либо в файле есть дыры. В журнале ядра тогда дополнительно появляется swapon: swapfile has holes. Решение: удалить файл и создать заново через dd, а не через fallocate.
На btrfs действует та же ошибка плюс BTRFS warning: swapfile must not be copy-on-write. Здесь порядок другой: файл нужно создать пустым и до заполнения пометить как не copy-on-write. Сразу важное замечание, от которого может зависеть судьба работающего сервера: truncate -s 0 и rm отрабатывают и в том случае, если по этому пути ещё подключён активный swap. После этого ядро ссылается на блоки, которых больше нет. Поэтому, если swapon --show показывает путь как активный, перед этим обязателен swapoff /swapfile.
truncate -s 0 /swapfile
chattr +C /swapfile
Дальше как обычно: заполнить через dd, затем chmod 600, mkswap, swapon. На сжатых подтомах и в снимках swap по-прежнему не работает.
swapon: /swapfile: swapon failed: Operation not permitted Вы находитесь в контейнере. LXC, OpenVZ и Docker используют общее ядро хоста и не имеют права включать собственный swap. Проверка:
systemd-detect-virt
Если команда сообщает kvm, qemu или none, работает собственное ядро и swap возможен. Если она сообщает lxc, openvz или docker, поможет только увеличение оперативной памяти или продукт с полноценной виртуализацией. KVM root-серверы и выделенные серверы KernelHost работают с собственным ядром, swap там настраивается без ограничений.
dd: error writing '/swapfile': No space left on device Накопитель переполнен. Сначала df -h, затем удалите наполовину записанный файл командой rm /swapfile, иначе он продолжит занимать место. Здесь тоже действует правило: если swapon --show показывает путь как активный, перед удалением нужен swapoff /swapfile.
Система больше не загружается, появляется аварийная консоль. Почти всегда это опечатка в /etc/fstab. Войдите через консоль в личном кабинете, затем mount -o remount,rw /, удалите ошибочную строку или скопируйте обратно /etc/fstab.bak, после чего перезагрузите. Именно для этого резервная копия и создавалась заранее.
Различия между Debian 13, Debian 12, Ubuntu 24.04 и 22.04
Команды на всех четырёх системах одинаковы, а вот исходное состояние нет.
- Уже имеющийся swap: Ubuntu Server из ISO-установщика часто создаёт
/swap.img, Debian из установщика — раздел подкачки. Облачные образы обоих дистрибутивов идут без swap. Всегда начинайте сswapon --show. - systemd-oomd: в Ubuntu начиная с 22.04 установлен и активен с завода, в обеих версиях Debian в стандартную поставку не входит. Это объясняет, почему одинаковое ПО на двух внешне одинаковых системах может умирать по-разному.
- База данных: Debian 12 и Debian 13 не поставляют
mysql-server, там всегда работает MariaDB (10.11 в Debian 12, 11.8 в Debian 13). В Ubuntu 22.04 и 24.04 есть и то, и другое. Значенияinnodb_buffer_pool_sizeпо умолчанию соответственно различаются, и именно этот параметр на небольших серверах чаще всего приводит к OOM. - Java: в Debian 12 есть только OpenJDK 17, в Debian 13 только OpenJDK 21, Ubuntu 22.04 и 24.04 покрывают версии с 8 по 21. JVM без заданного
-Xmxпо умолчанию забирает четверть оперативной памяти, и при нескольких экземплярах OOM-kill запрограммирован заранее. - Сообщения ядра: формат и формулировки сообщения об OOM на всех четырёх системах совпадают, приведённые выше примеры подходят везде.
- swappiness: везде 60 по умолчанию.
Когда swap помогает, а когда лишь откладывает проблему
Swap надёжно выручает при коротких пиках, например во время резервного копирования, обновления пакетов или ночного импорта. Он помогает со службами, которые занимают много памяти, а потом сутками ничего не делают: такие страницы вполне могут полежать на накопителе. И в критической ситуации он дарит вам те самые минуты, когда SSH-сессия ещё отвечает и вы можете вмешаться, вместо того чтобы смотреть на мёртвый сервер.
Swap не помогает, когда постоянная потребность в памяти просто превышает объём RAM. Тогда система начинает буксовать (thrashing): она непрерывно вытесняет страницы и загружает их обратно, load average уходит в двузначные значения, загрузка CPU остаётся низкой, всё ждёт I/O. На практике это хуже честного OOM-kill, потому что даже вход в систему перестаёт проходить. Две команды показывают, находитесь ли вы в этом состоянии:
vmstat 1 5
test -e /proc/pressure/memory && cat /proc/pressure/memory
У vmstat важны столбцы si и so. Постоянные трёхзначные значения означают активное вытеснение и обратную загрузку страниц. В /proc/pressure/memory решающим является full avg10: значения выше 10 говорят о том, что вся система десять процентов времени ждёт память. Выше 40 машина практически мертва. Правда, файл существует только тогда, когда в ядре активен механизм Pressure Stall Information. Штатные ядра Debian и Ubuntu его содержат, другие ядра не обязательно. Отсюда и подстраховка через test -e: если файла нет, вывод останется пустым, и вы не примете No such file or directory за неисправность. Дополнительно PSI включается параметром загрузки ядра psi=1.
Какие процессы действительно лежат в подкачке, покажет эта строка:
grep VmSwap /proc/*/status | sort -k2 -rn | head
Точно так же swap бесполезен против лимита cgroup (CONSTRAINT_MEMCG), против памяти, закреплённой через mlock, и против JVM, у которой heap настроен больше имеющегося RAM. Сборщик мусора регулярно проходит по всему heap и немедленно возвращает каждую вытесненную страницу обратно.
Три инструмента на случай, когда swap не спасает
zram создаёт сжатую область подкачки в самой оперативной памяти. Она на порядки быстрее файла на накопителе и в зависимости от характера данных даёт эффективный прирост памяти на 20-40 процентов:
apt update
apt install -y zram-tools
Настройка выполняется в /etc/default/zramswap через ALGO=zstd и PERCENT=50, после чего systemctl restart zramswap. Контроль через zramctl и снова через swapon --show, где теперь появится /dev/zram0. С zram параметр vm.swappiness следует поднять до 100-180, потому что вытеснение здесь обходится дёшево.
earlyoom вмешивается раньше, чем ядро подвесит систему, и завершает процессы прицельно, а не по эвристике:
apt install -y earlyoom
В /etc/default/earlyoom через EARLYOOM_ARGS задаются пороги и защищаются важные процессы, например так: -m 5 -s 5 --avoid '(^|/)(sshd|systemd)$'. Благодаря этому вход по SSH остаётся доступным, пока пожиратель памяти падает.
Жёсткие лимиты для отдельной службы являются самым аккуратным решением, если из рамок регулярно выходит одна конкретная служба. Через systemctl edit mariadb впишите MemoryHigh=1200M и MemoryMax=1500M. Тогда служба сначала притормаживается, а в крайнем случае завершается в одиночку, вместо того чтобы утащить за собой весь сервер. Контроль через systemctl show mariadb -p MemoryMax.
Но настоящим исправлением в большинстве случаев остаётся конфигурация приложения: innodb_buffer_pool_size в реалистичных пределах, pm.max_children исходя из фактического потребления памяти одним воркером, заданный -Xmx для каждой JVM. Swap — это страховочная сетка, а не решение.
Откат, если решение не подошло
Путь назад короткий, и проходить его надо именно в таком порядке:
swapoff /swapfile
При сильно заполненной подкачке команда может выполняться несколько минут, потому что все страницы обязаны вернуться в RAM. Если она упирается в swapoff: /swapfile: swapoff failed: Cannot allocate memory, значит в RAM нет места для обратного переноса и сначала придётся остановить службы. Только после успешного swapoff удаляйте строку из fstab и сам файл:
rm /swapfile
swapon --show
Удаление файла, пока он ещё подключён, приводит к системе, чьё управление памятью ссылается на несуществующий inode. Заканчивается это плохо. В завершение ещё раз free -h и перезагрузка в качестве контрольной проверки.
Частые вопросы
Сколько swap нужно на сервере?
Почему journalctl -k не показывает OOM-kill, хотя он был?
Как понять, кто завершил процесс: ядро или лимит systemd?
Почему swapon завершается с ошибкой «Invalid argument»?
Можно ли настроить swap в контейнере?
Означает ли vm.swappiness=0, что вытеснения в подкачку больше не будет?
2026 KernelHost GmbH. Все права защищены. Эта инструкция охраняется авторским правом. Публикация на других сайтах, в том числе частично или в изменённом виде, без нашего письменного согласия не разрешается. Цитирование с указанием источника и активной ссылкой мы приветствуем.

