
# Как заставить PM2 пережить перезагрузку сервера

Сервер перезагрузился, а сайт не поднялся. Он вернулся через час, когда кто-то запустил деплой. Выглядит как проблема деплоя, но это не она: деплой всего лишь ещё раз выполнил `pm2 start` руками.

Чтобы приложение поднималось само, нужны две отдельные вещи, и каждая из них без другой бесполезна.

## Две половины

**`pm2 save`** записывает текущий список процессов в `~/.pm2/dump.pm2`. PM2 поднимает только то, что лежит в этом файле, и ничего больше. Скрипт деплоя, который делает `pm2 delete` и `pm2 start`, но никогда не сохраняет, оставляет устаревший дамп — или пустой, если тот вообще ни разу не записывался.

**`pm2 startup`** ставит юнит systemd, который при загрузке выполняет `pm2 resurrect` и читает как раз этот дамп.

Одно без другого даёт либо загрузку, которая ничего не поднимает, либо сохранённый список, который никто не читает.

## Ловушка

Запуск `pm2 startup` от непривилегированного пользователя **не устанавливает вообще ничего**. Команда определяет systemd, собирает строку, которая поставила бы юнит, печатает её и завершается. Вывод длинный, зелёный, и заканчивается строкой о том, что команду можно скопировать. Читается как успех. Не произошло ничего.

Значит: запускать от root, а проверять через systemd, а не по тому, что напечатал PM2.

## Правильный порядок

Приложение должно работать до сохранения, иначе вы сохраните пустой список.

```bash
# от <user> — убедиться, что приложение работает
pm2 status
```

```bash
# от root — поставить юнит для этого пользователя
env PATH=$PATH:/usr/bin pm2 startup systemd -u <user> --hp /home/<user>
```

Префикс `env PATH=$PATH:/usr/bin` — не украшение. В `PATH` root обычно нет Node, а юнит запоминает тот путь, с которым был сгенерирован. Без префикса юнит существует, но при загрузке не находит `node`.

```bash
# снова от <user> — записать дамп
pm2 save
```

## Проверяйте половины по отдельности

Ломаются они независимо, поэтому и проверять их надо независимо.

```bash
systemctl is-enabled pm2-<user>
```

Должно вывести `enabled`. Всё остальное, будь то `disabled` или `Failed to get unit file state`, означает, что юнит так и не был установлен, что бы вам ни сообщал PM2.

```bash
grep -o '"name":"[^"]*"' /home/<user>/.pm2/dump.pm2
```

Здесь должен появиться `<app>`. Если файла нет или grep ничего не печатает, значит `pm2 save` ни разу не выполнялся при запущенном приложении.

## Проверка без перезагрузки

Чтобы узнать, работает ли это, перезагружать боевую машину не нужно.

```bash
# от <user>
pm2 kill
```

```bash
# от root
systemctl start pm2-<user>
```

```bash
# от <user>
pm2 status
curl -sf http://127.0.0.1:3000/health
```

Если `<app>` снова в статусе online, работают обе половины. При загрузке происходит ровно это же.

**Никогда не выполняйте `pm2 save` после `pm2 kill`.** При убитом демоне список процессов пуст, и сохранение записывает эту пустоту в дамп, уничтожая ровно то, что вы проверяли. Если сделали по привычке — поднимите приложение и выполните `pm2 save` заново.

## Добавьте `pm2 save` в скрипт деплоя

Дамп расходится с реальностью, как только деплой меняет процесс. Если ваш скрипт удаляет и запускает заново, он обязан сохранить после этого:

```bash
pm2 delete <app> || true
pm2 start ecosystem.config.cjs --only <app>
pm2 save
```

`|| true` важен при первом запуске, когда удалять нечего и `pm2 delete` завершается ненулевым кодом, а под `set -e` это убивает весь скрипт.

Без последней строки всё вышеописанное продолжает работать — ровно до первого деплоя, который переименует приложение, изменит число инстансов или поменяет точку входа. После этого дамп описывает процесс, которого больше нет, и следующая перезагрузка воскрешает именно его.

## Ещё о порядке загрузки

Сгенерированный юнит содержит `After=network.target`. Он не выстроен после вашей базы данных. При холодном старте приложение может подняться раньше, чем MariaDB или PostgreSQL начнут принимать соединения, упасть на первом же запросе и завершиться.

Обычно это решается само: PM2 перезапускает процесс, ко второй или третьей попытке база готова, и сайт поднимается. Признак — счётчик перезапусков больше нуля у в остальном здорового процесса, колонка `↺` в `pm2 status`.

Если восстановления не происходит, задайте порядок явно:

```bash
sudo systemctl edit pm2-<user>
```

```ini
[Unit]
After=network.target mariadb.service
```

Добавляйте это только если сами видели сбой. Поставив юнит за медленным сервисом или за сервисом на другой машине, вы меняете загрузку, которая повторяет попытку, на загрузку, которая просто зависает.

[Проверить сайт](/ru) · [Другие статьи](/ru/blog) · [Об AgentReady](/ru/about)
