Node-приложение после перезапуска падает с EADDRINUSE: порт держит предыдущий процесс, которому не дали завершиться

Ошибка Error: listen EADDRINUSE: address already in use :::3000 при перезапуске через pm2, systemd или compose. Приложение то запускается, то нет, а иногда уходит в цикл перезапусков.

Что было в обсуждении. Приложение запускалось через bash-обёртку, которая вызывала node с транспилятором. При pm2 restart pm2 посылал сигнал завершения обёртке, но сам node-процесс сигнал не получал, порт оставался занят, и новый экземпляр падал на listen. Сопровождающие указали на три вещи: не оборачивать процесс в скрипт (или использовать exec в конце скрипта, чтобы node заменил bash и получал сигналы напрямую), увеличить время ожидания завершения (kill_timeout), и обрабатывать SIGTERM в приложении, закрывая сервер и соединения.

Минимальная корректная остановка в приложении.

const server = app.listen(PORT);
process.on('SIGTERM', () => {
  server.close(() => process.exit(0));
  setTimeout(() => process.exit(1), 10000).unref();
});

server.close() перестаёт принимать новые соединения и завершает процесс после закрытия текущих; таймер на всякий случай не даёт зависнуть навсегда.

Со стороны менеджера процессов.

  • pm2: kill_timeout в конфигурации больше, чем нужно приложению на закрытие; wait_ready и listen_timeout, если хотите, чтобы pm2 ждал готовности нового экземпляра.
  • systemd: KillSignal=SIGTERM, TimeoutStopSec=15, Restart=on-failure, RestartSec=2. Слишком маленький RestartSec даёт как раз цикл «порт ещё занят, снова упал».
  • Docker/compose: контейнер получает SIGTERM, но если процесс запущен через shell-форму CMD node app.js внутри sh -c, сигнал до node не дойдёт; используйте exec-форму CMD ["node", "app.js"].

Кто держит порт прямо сейчас. ss -ltnp | grep :3000 или lsof -i :3000 покажут процесс; pm2 list покажет забытый экземпляр под другим именем.

Статус. Причина названа в обсуждении сопровождающими и совпадает с документацией; подтверждение автора не показано.

Сигналы и чистый перезапуск описаны в документации pm2.