Каждое Node.js-приложение, которое живёт в проде дольше недели, рано или поздно задаёт один и тот же вопрос: что будет держать его на плаву при краше и что поднимет его заново после перезагрузки сервера? Два ответа доминируют - PM2, нативный для Node менеджер процессов, и systemd, инит-система, которая уже живёт внутри практически любого современного дистрибутива Linux.
Они конкурируют не совсем на одном уровне. PM2 - это userspace-демон, который вы ставите поверх ОС. systemd - это сама ОС. Понимание этой границы - разница между конфигурацией, которая годами работает молча, и той, что воюет с вами при каждом деплое. В этом гайде мы разберём реальное поведение обоих на основе официальной документации, чтобы вы выбирали осознанно.
Что это вообще такое
PM2 - демон-менеджер процессов, написанный под Node.js, устанавливается через npm. Вы ставите его командой npm install pm2@latest -g, а запускаете приложение через pm2 start app.js. PM2 демонизирует процесс, держит его живым и даёт CLI для просмотра статуса, хвостов логов и релоада приложения.[1] Его главная «фишка» под Node - кластерный режим: с флагом -i он раскидывает нагрузку по всем ядрам CPU без единой правки кода.[2]
systemd - менеджер сервисов и инит-система, которую используют Ubuntu, Debian, Arch, Fedora и большинство остальных дистрибутивов. Он управляет всеми сервисами машины - сетью, аналогами cron, базами данных, чем угодно - через unit-файлы. Ваше Node.js-приложение - это просто ещё один .service unit, которым он управляет ровно тем же механизмом, что держит живой всю ОС.
Практический вывод: для systemd ничего устанавливать не нужно, и он запускает ваш процесс тем же инструментарием и с тем же хардненингом, что и остальную систему.
Запуск приложения и честный признак «поднялось»
Тривиальный случай для PM2 - одна команда: pm2 start app.js.[1]
npm install pm2@latest -g
pm2 start app.js --name my-api
В systemd вы пишете unit-файл, кладёте его в /etc/systemd/system/ и говорите systemd запустить и включить сервис:
[Unit]
Description=My Node.js API
[Service]
Type=exec
User=www
WorkingDirectory=/srv/my-api
ExecStart=/usr/bin/node /srv/my-api/server.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now my-api.service
Различия здесь важны, как только вы начинаете заботиться о правдивой готовности. Опция systemd Type= определяет, как менеджер решает, что unit закончил старт. Type=simple - значение по умолчанию, но сама документация systemd рекомендует Type=exec: он считает unit поднятым только после реального исполнения бинарника, поэтому systemctl start честно сообщает об ошибке, когда бинарник отсутствует или User= не существует, вместо ложного успеха.[3] Это реально полезная гарантия корректности, которую вы получаете бесплатно.
PM2 сообщает «online» с точки зрения собственного демона. Обычно этого достаточно, но если вы опираетесь на сигналы готовности в оркестрации, учтите: картина PM2 и реальное состояние приложения могут расходиться, пока вы сами не навесите health-чеки.
Автозапуск при загрузке
Менеджер процессов, исчезающий после ребута, хуже, чем никакого. Здесь философии расходятся радикально.
PM2 сам по себе ребут не переживает. Надо сгенерировать стартовый скрипт через pm2 startup - он сам определит вашу инит-систему и на современных дистрибутивах создаст systemd-юнит, который владеет демоном PM2. Затем pm2 save замораживает текущий список процессов, чтобы PM2 всё перезапустил после загрузки.[4] Это ритуал из двух шагов, и его стоит взять в привычку, иначе ребут молча убьёт то, что вы считали управляемым.
В systemd персистентность при загрузке - по дизайну. systemctl enable создаёт симлинки, запускающие unit при старте, а WantedBy=multi-user.target в секции [Install] - стандартный хук. Никакого лишнего инструмента и шага «save» - сам unit-файл и есть декларация автозапуска.[3] Поскольку systemd - первый процесс, который запускает ядро (PID 1), он по определению «последний из живых»: ваше приложение не может подняться раньше самой ОС.
Обработка крашей и рестарты
Если процесс умирает, перезапустят оба - но дефолты и ручки настройки разные.
PM2 перезапускает по умолчанию и показывает это поведение в pm2 list. Доступны опции вроде --max-memory-restart <200MB> для перезапуска процесса при превышении порога памяти и --restart-delay для разнесения авторестартов по времени.[1] Для однопроцессного Node-приложения этой устойчивости часто хватает с головой, а pm2 monit даёт живой обзор из коробки.
systemd консервативнее: Restart= по умолчанию равен no - перезапускать сервис systemd не будет, пока вы сами не включите это. Ставьте Restart=on-failure (перезапуск при грязном выходе) или Restart=always. Тайминг управляется RestartSec=, у которого дефолт 100 мс - почти всегда слишком агрессивно, поэтому обычно его поднимают до пары секунд, чтобы приложение успело, скажем, отпустить порт и подчистить сокеты.[5]
Две детали systemd стоит проговорить, потому что их чаще всего упускают. RestartSec= применяется не только к Restart= - он также регулирует экспоненциальный бэкофф RestartMaxDelaySec/RestartSteps, то есть необычно частые краши получают всё больший прогрессивный откат, а не долбятся в плотном цикле.[5] А для Type=oneshot сервисов Restart=always и Restart=on-success вообще запрещены - вряд ли столкнётесь с этим, если ваш Node-процесс - долгоживущий сервер, но знать стоит, прежде чем копировать сниппет впрок.[5]
Масштабирование по ядрам и кластеризация
Это единственная область, где PM2 для Node-приложений действительно сияет, - и причина, по которой многие команды берут его вообще.
Кластерный режим PM2 изнутри использует модуль cluster Node.js и раскидывает сетевое приложение по всем CPU без изменения исходников. pm2 start app.js -i max сам определяет число ядер и запускает столько процессов, сколько доступно.[2] А pm2 reload даёт релоад без даунтайма - для сетевых приложений - за счёт поочерёдной смены воркеров, тогда как restart гасит и поднимает весь пул разом.[2] Если ваш деплой-процесс требует раскатки без простоя, это из коробки почти нечем побить.
В systemd ничего такого бесплатно нет. Он охотно супервизирует N копий процесса (N unit-файлов, либо шаблонный unit вида my-api@.service), но понятия «кластерного режима» у него нет, и порт или балансировку соединений между копиями он никак не делит. Чтобы получить тот же результат, кластеризацию придётся реализовать самому на модуле cluster.
Документацию Node.js по кластеру стоит прочесть до того, как решить, что «больше воркеров - всегда быстрее». Модуль поддерживает два способа раздачи соединений: round-robin, где первичный процесс слушает порт и отдаёт подключения воркерам по очереди (дефолт на всех платформах, кроме Windows), и подход с общим сокетом. Документация честно признаёт, что распределение у подхода с общим сокетом на практике бывает сильно разбалансированным - в наблюдениях свыше 70% соединений ложилось всего на два процесса из восьми.[6] Кластер - это мощно, но это не «заведи побольше - и строго быстрее», и приложение обязано быть stateless (сессии, сокеты, in-memory-кэши - в общем хранилище), чтобы кластер от него был. Собственная документация PM2 говорит ровно то же: кластерное приложение настолько хорошо, насколько оно stateless.[2]
Лимиты ресурсов
Если нужен жёсткий потолок по памяти или CPU процесса, сильнее systemd, потому что он сидит прямо на механизме cgroup ядра.
systemd изнутри использует Linux Control Groups (cgroups), а его директивы управления ресурсами (лимиты вида MemoryMax=, CPUWeight=, TasksMax= и многие другие) настраиваются в секции [Service] unit-файла. Контроллеры включаются автоматически в момент, когда вы задали соответствующую опцию: например, установка CPUWeight= включает контроллер cpu, а лимит задач - контроллер pids.[7] Можно ограничить Node-приложение 512 МБ памяти, зажать долю CPU или число файловых дескрипторов - и предел удержится на уровне ядра.
PM2 жёсткие cgroup-лимиты на уровне ядра навязать не может. Ближайший инструмент - порог --max-memory-restart, который реагирует: убивает и перезапускает процесс постфактум, а не мешает ему превысить лимит. Для железной гарантии чисто побеждает systemd.
Логи и наблюдаемость
Логи дают оба; разница в том, где они живут и что рядом получаешь.
PM2 сам владеет логами: pm2 logs стримит вывод, --lines 200 копает историю, умеет писать в отдельные файлы по приложениям и добавлять метки времени. Он стыкуется с платным дашбордом pm2.io для метрик по нескольким серверам.[1] Команде, привыкшей к семантике pm2, это без трения.
systemd всё пишет в journal, который читается через journalctl -u my-api.service. Поскольку journal покрывает всю систему, краш Node коррелируется с событиями ядра и других сервисов в одном месте - этого изолированные логи PM2 дать не могут. Связать stderr вашего приложения с OOM-killer’ом и сетевыми событиями - реально мощно при диагностике аварий, и работает оно без конфигурации.
Об ограничении привилегий
Раз systemd запускает ваш процесс как системный сервис, вы бесплатно получаете его профиль хардненинга. Опции исполнения живут в systemd.exec, и типовой прод-юнит запускает приложение под непривилегированным пользователем (User=www), отключает общий /tmp (PrivateTmp) и запрещает получение новых привилегий (NoNewPrivileges=). Это те же ограничения, что применяются к прочим системным сервисам, и объявляются они парой строк в unit-файле.[8] PM2 запускает процесс с привилегиями пользователя, поднявшего демона, поэтому бремя минимальных привилегий ложится на вас.
Честная рекомендация
Универсального победителя нет: инструменты решают разные части задачи, и лучшие конфигурации обычно используют оба.
Берите PM2 (возможно, поверх демона PM2 под управлением systemd), когда нужны кластеризация и релоад без даунтайма без правки кода, когда хочется Node-нативный инструментарий (pm2 list, pm2 logs, pm2 monit) или когда ваша команда уже свободно в нём. Это самый быстрый путь к многопроцессорному прод-набору для stateless Node-API.
Берите systemd, когда отдача однопроцессная или нативно кластеризована в приложении, когда нужны лимиты на уровне ядра и общая по системе корреляция journal, когда приложение должно управляться тем же хардненингом, что и остальная ОС, либо когда важен принцип «не плодить лишних движущихся частей»: systemd не требует демона, телеметрии pm2 start, зависимости от node_modules и памяти pm2 save.
Практическая заметка: слой персистентности самого PM2 и есть systemd-юнит - pm2 startup на современном дистрибутиве генерирует pm2-<user>.service, который владеет демоном.[4] Так что немалая часть спора «PM2 vs systemd» - это «systemd-юнит, запускающий ваш Node», против «systemd-юнита, запускающего PM2, который запускает ваш Node». Если вы уже приняли systemd на уровне загрузки, PM2 поверх - это лишний слой, который вы оплачиваете сложностью и получаете взамен только кластеризацию и его CLI.
И наконец, любое утверждение в духе «Node.js это нормально переварит» верно только на поддерживаемом рантайме. Документация Node.js прямо говорит, что прод-приложения должны использовать только релизы Active LTS или Maintenance LTS, а LTS гарантирует исправление критических багов суммарно в течение 30 месяцев.[9] Какой бы менеджер вы ни выбрали, закрепите приложение на LTS-ветке - на момент написания это Node.js 24 (Krypton) и 22 (Jod), а v26 ещё в статусе Current.[9]
Источники
- PM2 - Quick Start (установка, опции CLI, логи, monit). https://pm2.keymetrics.io/docs/usage/quick-start/ - checked 2026-09-28
- PM2 - Cluster Mode (масштабирование кластера,
-i max, релоад без даунтайма, требование stateless). https://pm2.keymetrics.io/docs/usage/cluster-mode/ - checked 2026-09-28 - systemd.service - семантика
Type=, рекомендацияType=exec, структура unit-файла. https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html - checked 2026-09-28 - PM2 - Startup Script (
pm2 startup, автоопределение systemd,pm2 save). https://pm2.keymetrics.io/docs/usage/startup/ - checked 2026-09-28 - systemd.service -
Restart=(дефолтno),RestartSec=(дефолт 100 мс), бэкофф/троттлинг. https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html - checked 2026-09-28 - Node.js - модуль Cluster (дефолт round-robin, наблюдение о дисбалансе общего сокета). https://nodejs.org/api/cluster.html - checked 2026-09-28
- systemd.resource-control - управление ресурсами на cgroups, авто-включение контроллеров. https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html - checked 2026-09-28
- systemd.exec - окружение исполнения и опции хардненинга. https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html - checked 2026-09-28
- Node.js - Releases (требование Active LTS / Maintenance LTS, гарантия 30 месяцев, статус v24/v22/v26). https://nodejs.org/en/about/previous-releases - checked 2026-09-28