Ошибка Cannot fork: Resource temporarily unavailable обычно означает, что процесс попытался создать дочерний процесс через fork()/clone(), но ядро вернуло EAGAIN. В продакшене это почти всегда не «кончилась память», а сработал лимит на количество задач (процессов и потоков) на одном из уровней: ulimit -u, systemd или cgroups.
Что означает Cannot fork (EAGAIN) и что именно считается «процессом»
В Linux под лимиты часто попадают не только «процессы» в бытовом смысле, но и потоки: в ядре это задачи, которым нужен PID/запись в таблице. Поэтому один сервис может иметь «мало процессов», но тысячи потоков и внезапно упереться в ограничение.
Отличие от проблем с памятью простое: при нехватке памяти чаще встречаются ENOMEM, OOM-killer и сообщения в dmesg про убийство процессов. При EAGAIN чаще виноваты политики/лимиты.
Где чаще всего срабатывает лимит: ulimit, systemd, cgroups v2
Ограничения на количество задач могут накладываться одновременно, и сработает самое «узкое» место:
- RLIMIT_NPROC — то, что показывает
ulimit -u(его часто называютnproc). - systemd — лимиты юнита:
TasksMax(задачи в cgroup) иLimitNPROC(обёртка над RLIMIT_NPROC для процесса сервиса). - cgroups v2 — контроллер pids:
pids.max(лимит задач в группе) иpids.current(текущая занятость).
Практическая цель диагностики: определить, какой именно уровень ограничил создание задач, затем найти «кто раздулся», и только после этого менять параметры.

Быстрые проверки: подтвердить лимит и понять, где он стоит
1) Проверка RLIMIT_NPROC (ulimit -u) в текущей сессии
Если ошибка проявляется в интерактивной SSH-сессии (у конкретного пользователя не запускаются команды), начинайте с этого:
ulimit -u
ulimit -a | grep -E 'processes|max user processes'
Если лимит вроде 1024 или 4096, а у вас воркеры/очереди/Java/CI или много параллелизма — это частая причина.
2) Проверка systemd-лимитов для сервиса
Если ломается только один демон под systemd, смотрим его ограничения:
systemctl show -p TasksMax -p TasksCurrent -p LimitNPROC your-service.service
systemctl status your-service.service
TasksMax ограничивает количество задач (процессы+потоки) в cgroup юнита. LimitNPROC ограничивает число процессов для UID (через RLIMIT) в контексте сервиса.
3) Проверка cgroups v2: pids.max и pids.current
Сначала убедитесь, что у вас unified hierarchy (cgroups v2):
stat -fc %T /sys/fs/cgroup
Если видите cgroup2fs, это v2. Дальше узнайте cgroup сервиса и посмотрите лимиты:
systemctl show -p ControlGroup your-service.service
Допустим, вернулось /system.slice/your-service.service. Тогда:
CG=/sys/fs/cgroup/system.slice/your-service.service
cat $CG/pids.max
cat $CG/pids.current
Если pids.current близко к pids.max, причина практически подтверждена.
Как отличить ulimit -u от pids.max и TasksMax: типичные признаки
- Проблема у конкретного пользователя: в его shell не стартуют даже простые команды. Часто виноват
ulimit -uи настройки PAM limits. - Проблема только у одного systemd-сервиса: система в целом жива, но воркеры не создаются. Чаще всего это
TasksMax,LimitNPROCилиpids.maxего cgroup. - Проблема в контейнере: почти всегда это лимит pids на уровне cgroup контейнера или
TasksMaxдля scope/юнита контейнера на хосте.
Кто «съел процессы»: практическая диагностика
Топ по числу процессов по пользователям
ps -eo user= | sort | uniq -c | sort -nr | head
Так вы быстро увидите UID, под которым расплодились процессы (часто это сервисный пользователь, CI-агент или веб-пользователь).
Топ по потокам (когда «процессов немного», но лимит всё равно сработал)
Многие лимиты считают tasks, то есть потоки тоже. Поэтому полезно сравнить:
ps -e --no-headers | wc -l
ps -eL --no-headers | wc -l
ps -eLo user= | sort | uniq -c | sort -nr | head
Если ps -e выглядит терпимо, а ps -eL показывает в разы больше — упёрлись в потоки или в общий лимит задач.
Сколько потоков у конкретного процесса
PID=1234
ls /proc/$PID/task | wc -l
Тысячи потоков — повод проверять настройки пулов, лимиты воркеров и утечки (особенно в Java, Node.js, PHP-FPM, пайплайнах CI).
Если это systemd-сервис: текущие tasks и лимит
systemctl show -p TasksCurrent -p TasksMax your-service.service
Если TasksCurrent близко к TasksMax, дальше бессмысленно «перезапускать до победы»: нужно уменьшать параллелизм или повышать лимит.
Если подозреваете зацикливание от cron/таймеров
Частая причина «внезапного» размножения процессов — ошибка в cron или systemd-timer. Если у вас есть такие задачи, держите под рукой разбор и диагностику: cron, crontab и systemd timers: как найти источник бесконечных запусков.
Частые причины исчерпания PID/tasks
- Зацикленный скрипт/супервизор: бесконечный цикл запуска команды без паузы и без контроля завершения.
- Слишком много воркеров: например, десятки инстансов template-юнитов или чрезмерный параллелизм в очередях.
- Переоценённый веб-пул: завышенные
workers/childrenв веб-сервере или PHP-FPM, плюс потоки внутри каждого воркера. - Ошибочная «fork-bomb»: рекурсия в shell/CI job, неверная обработка ошибок, отсутствие блокировок.
- Налипание процессов: короткие процессы копятся, потому что что-то блокируется на I/O, DNS, файловых блокировках или очередях.
Критерий из практики: если число задач растёт ступеньками и почти не падает — вероятно, утечка или зацикливание. Если растёт строго вместе с нагрузкой — чаще неверный sizing и отсутствие backpressure.
Как безопасно увеличить лимиты (и где именно это делать)
Повышайте лимиты аккуратно: они защищают хост от полного отказа, когда «размножение» выходит из-под контроля. Хорошая последовательность: сначала остановить источник роста, затем подобрать разумные значения лимитов.
1) Временно поднять ulimit -u в текущей сессии
ulimit -u 16384
Это помогает для диагностики и кратковременной стабилизации, но не является постоянным решением.
2) Постоянно настроить nproc через PAM limits
Обычно это делается в /etc/security/limits.d/ (конкретный файл и приоритет зависят от дистрибутива):
# /etc/security/limits.d/90-nproc.conf
appuser soft nproc 16384
appuser hard nproc 32768
После изменения переподключите пользователя или перезапустите сервис, чтобы лимиты применились.
3) Поднять LimitNPROC для systemd-сервиса
Если сервис запускается systemd, правильнее фиксировать лимиты в override:
systemctl edit your-service.service
Содержимое drop-in:
[Service]
LimitNPROC=32768
Применение:
systemctl daemon-reload
systemctl restart your-service.service
4) Поднять TasksMax (и тем самым лимит tasks в cgroup юнита)
Если упираетесь именно в задачи (потоки тоже считаются), повышайте TasksMax:
systemctl edit your-service.service
[Service]
TasksMax=20000
systemctl daemon-reload
systemctl restart your-service.service
Значение TasksMax=infinity иногда применяют, но безопаснее поставить расчётный предел, чтобы сохранить предохранитель.
Когда имеет смысл трогать pids.max напрямую
Правка pids.max в /sys/fs/cgroup часто временная: после перезапуска юнита/контейнера её перезапишет systemd или runtime. В мире systemd предпочтительнее управлять через TasksMax (или настройки slice), а в контейнерах — через параметры запуска/оркестратора.

Типовые сценарии: короткие планы действий
Сценарий A: Cannot fork в SSH-сессии конкретного пользователя
- Проверьте
ulimit -uв этой сессии. - Посчитайте процессы/потоки пользователя (агрегацией по user или
ps -u appuser). - Найдите источник (cron/скрипт/CI) и остановите его.
- Только после стабилизации фиксируйте постоянный лимит через PAM limits.
Сценарий B: падает только systemd-сервис
- Посмотрите
TasksMax,TasksCurrent,LimitNPROC. - Если упёрлись в
TasksMaxилиpids.max— поднимайтеTasksMaxи параллельно снижайте лишний параллелизм в конфиге. - Если упёрлись в
LimitNPROC— поднимайтеLimitNPROCи проверьте PAM limits, если сервис стартует от конкретного пользователя.
Если это сервис с воркерами (очереди, супервизор, template-юниты), полезно навести порядок в модели воркеров: как правильно управлять воркерами через supervisor/systemd и не устроить лавину процессов.
Сценарий C: ошибка внутри контейнера
Проверьте с хоста, какой лимит задач выставлен для scope/юнита контейнера и чему равны pids.current/pids.max в его cgroup. Дальше либо увеличивайте лимит на уровне runtime, либо уменьшайте параллелизм внутри контейнера (число воркеров, потоки, пул).
Почему «просто поднять лимит» иногда делает хуже
Лимит задач защищает систему от деградации. Если убрать предохранитель и не устранить причину размножения, вы рискуете получить рост load average, проблемы с запуском диагностических утилит (они тоже не смогут fork), а затем каскадные отказы сервисов.
Правильный подход: остановить источник роста, затем выставить разумные лимиты и добавить мониторинг по числу задач до достижения порога.
Мини-чеклист на инцидент
- Определите область: весь хост, один пользователь или один сервис.
- Проверьте
ulimit -u(для user-уровня) иLimitNPROC(для systemd-сервиса). - Проверьте
TasksMax,TasksCurrentи (в cgroups v2)pids.max/pids.current. - Найдите виновника по процессам и потокам и остановите источник (скрипт, таймер, воркеры, неправильный пул).
- Зафиксируйте постоянные настройки через systemd override или PAM limits, а не ручной записью в
/sys/fs/cgroup. - Добавьте алерты: рост tasks по сервису/пользователю и предупреждение до достижения лимита.
Итог
Cannot fork: Resource temporarily unavailable и EAGAIN почти всегда означают упор в лимит задач: ulimit -u (RLIMIT_NPROC), systemd (TasksMax, LimitNPROC) или cgroups v2 (pids.max). Быстро определите уровень срабатывания, найдите «кто раздулся», стабилизируйте систему и только затем аккуратно повышайте лимит там, где это действительно нужно.


