Короткий ответ: на VPS с Ubuntu 24.04 Node.js ставится через mise командой mise use -g node@lts — это LTS 24, на момент гайда latest v24.21.0, около 10 минут вместе с установкой самого mise. Не используйте apt install nodejs (там 18.19.1 EOL) и не подключайте NodeSource: при mise он не нужен.
Что понадобится
- Ubuntu 24.04 LTS (noble), пользователь sudo, вход по ключу — см. первую настройку VPS;
- mise с activate в bashrc — см. установку mise; если mise ещё нет, ниже есть минимальный блок;
- открытые исходящие HTTPS; входящие 3000 в UFW не открывать;
- 10 минут.
Документация Node.js: nodejs.org. Документация mise: mise.jdx.dev. К концу статьи у вас будет LTS 24 в пользовательском каталоге, проверка node -v / npm -v, крошечный HTTP-сервер на 127.0.0.1:3000 и systemd-юнит, который переживает reboot. Наружу порт 3000 не торчит — это сознательно.
Команды копируйте целиком. Если проверка шага не совпала с ожиданием — остановитесь. Типичная развилка: вы всё ещё в той SSH-сессии, где не было activate, и смотрите на /usr/bin/node вместо mise.
Почему не apt и не NodeSource
В репозиториях Ubuntu 24.04 пакет nodejs — это 18.19.1. К сентябрю 2026 это EOL: без нормальных обновлений безопасности и без современных API. Многие HOWTO до сих пор предлагают NodeSource — сторонний репозиторий, который борется с apt за один и тот же /usr/bin/node. На сервере с mise это лишняя точка поломки: apt обновляет «системный» node, mise держит свой, systemd берёт третий путь.
| Способ | Что получите на 24.04 | Ставить? |
|---|---|---|
| apt install nodejs | 18.19.1 EOL | нет |
| NodeSource | свой репозиторий в /usr | нет, если есть mise |
| nvm | Node в $HOME, конфликт PATH с mise | нет |
| mise use -g node@lts | LTS 24, v24.21.0 | да |
Если node уже стоит из apt, сначала проверьте:
command -v node
node -v
dpkg -l nodejs npm 2>/dev/null | grep ^ii
Если видите v18.19.1 и /usr/bin/node — не маскируйте это nvm. Снимите пакет после того, как убедитесь, что скрипты системы его не вызывают, либо просто не кладите /usr/bin впереди shims: mise activate должен стоять в bashrc, новая сессия, type node показывает shim.
Шаг 1. mise, если его ещё нет
Если mise —version уже отвечает v2026.9.6 — переходите к шагу 2. Если нет:
curl -fsSL https://mise.run | sh
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc
Закройте SSH и зайдите снова. PPA mise на Ubuntu 24.04 не подключайте (он для 26.04+). Проверка:
mise --version
mise doctor
Шаг 2. Ставим Node.js LTS
mise use -g node@lts
node -v
npm -v
command -v node
mise which node
Ожидание node -v: v24.21.0 (LTS-линия 24). npm -v должен напечатать номер встроенного npm — отдельно apt-пакет npm не ставьте. command -v node — shim в ~/.local/share/mise/shims/node. mise which node — реальный файл в ~/.local/share/mise/installs/node/24.21.0/bin/node. Для systemd нужен второй путь, не первый.
Именно mise use -g, не только mise install. install качает, use ещё и фиксирует глобальную версию. Без use в новом терминале снова «command not found» или старый /usr/bin/node.
Проверка, что это не apt:
node -p "process.version + ' ' + process.execPath"
execPath должен жить в ~/.local/share/mise/installs/…, не в /usr/bin/node. Если напечаталось v18.19.1 — вы смотрите системный пакет: либо не открыли новую сессию после activate, либо в bashrc ниже сидит nvm, либо mise use не выполняли.
Идентификатор node@lts всегда указывает на текущий LTS. На 13–14 сентября 2026 это линия 24 и конкретный latest v24.21.0. Когда выйдет следующий патч той же линии, повторный mise use -g node@lts подтянет его. Если нужно заморозить именно 24.21.0 в проекте, в каталоге приложения выполните mise use node@24.21.0 — появится локальный .tool-versions или mise.toml, и чужой LTS-апдейт этот каталог не заденет.
Глобальный npm i -g на сервере не используйте как способ доставить приложение. Пакеты проекта ставьте в его каталог (npm ci / npm install рядом с package.json). Глобальные кли (pnpm, например) — через mise, не через npm -g под sudo: sudo уходит в /usr/local и снова расходится с User= в systemd.
pnpm: не corepack, а mise
В Node.js 24 corepack ещё есть, но помечен как experimental. В Node.js 25 его убрали. Ставить pnpm через corepack enable на LTS 24 — привычка, которая сломается при следующем major. Канон этой серии: pnpm тоже через mise.
mise use -g pnpm
pnpm -v
mise which pnpm
Не фиксируйте в статье и в юнитах «магический» npm i -g pnpm под sudo: глобальный npm -g пишет не туда, куда вы думаете, и снова расходится с systemd. yarn/pnpm как плагины mise переживают смену каталога и смену версии Node.
Шаг 3. Hello на localhost
Проверьте, что рантайм реально запускает процесс, до того как писать юнит.
mkdir -p ~/hello-node
cat > ~/hello-node/server.js <<'EOF'
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('ok\n');
}).listen(3000, '127.0.0.1');
console.log('listen 127.0.0.1:3000');
EOF
node ~/hello-node/server.js
Во втором окне:
curl -sS http://127.0.0.1:3000/
Ответ — ok. С вашей домашней машины curl http://IP:3000 должен не открываться: порт в UFW закрыт, процесс слушает 127.0.0.1. Это правильно. Наружу hello выведет nginx (см. гайд по Certbot), не сам Node.
Остановите процесс Ctrl+C. Если «listen EADDRINUSE» — кто-то уже занял 3000: ss -tlnp | grep 3000. Не меняйте listen на 0.0.0.0 «чтобы проверить с ноутбука»: для проверки есть ssh -L 3000:127.0.0.1:3000 deploy@IP и curl уже с вашей машины на localhost. Так вы не учитесь открывать прикладной порт в UFW.
package.json для этого hello не обязателен. Когда появится настоящий сервис, держите его в отдельном каталоге, с собственным package-lock.json, и запускайте node того же mise, что в юните. Не ставьте второй Node «для этого проекта» через NodeSource — локальную версию фиксирует mise use без -g.
Шаг 4. systemd с абсолютным путём
Скопируйте вывод mise which node целиком. Подставьте своего пользователя и путь. Не вставляйте shim.
mise which node
# пример: /home/deploy/.local/share/mise/installs/node/24.21.0/bin/node
Юнит (пути замените на свои):
sudo tee /etc/systemd/system/hello-node.service <<'EOF'
[Unit]
Description=hello-node
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/hello-node
ExecStart=/home/deploy/.local/share/mise/installs/node/24.21.0/bin/node /home/deploy/hello-node/server.js
Restart=on-failure
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now hello-node
sudo systemctl status hello-node --no-pager
curl -sS http://127.0.0.1:3000/
User= обязан совпасть с владельцем каталога installs. Если mise ставили под deploy, а в юните User=root (или наоборот) — ExecStart не найдёт файл. После enable —now status должен быть active, curl — снова ok. Логи: journalctl -u hello-node -e.
Когда смените patch-версию Node, путь 24.21.0 в ExecStart устареет. Снова mise which node, правка юнита, daemon-reload, restart. Не пытайтесь обойти это через shims: в systemd их нет. Не ставьте в юнит ExecStart=/bin/bash -lc ‘node server.js’: login-shell тянет bashrc, который в сервисах то читается, то нет, и маскирует кривой PATH.
Проверка автозапуска: systemctl is-enabled hello-node должен сказать enabled. После reboot (когда будете готовы) снова curl на 127.0.0.1:3000. Если сервис не поднялся — journalctl -u hello-node -b за текущую загрузку. Частые причины: опечатка в абсолютном пути, User= не тот, WorkingDirectory не существует, файл server.js не у того владельца.
UFW по-прежнему с 22/80/443. Когда появится домен, nginx будет проксировать на 127.0.0.1:3000, сертификат — по гайду Certbot. Сам Node до этого момента наружу не высовывается.
Проверка результата
node -v
npm -v
mise current
mise which node
systemctl is-active hello-node
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/
Ожидание: v24.21.0, current содержит node, is-active = active, HTTP 200. Если node -v другой — смотрите type node и nvm/apt в PATH.
Частые ошибки
- apt install nodejs. Получите 18.19.1 EOL. Удалите пакет, поставьте node@lts через mise, проверьте execPath.
- NodeSource «как во всех статьях». При mise не нужен. Два node в /usr и в ~/.local — классический «на сервере одна версия, в systemd другая».
- ExecStart=/usr/bin/node или shim. Только вывод mise which. Shim без activate в юните не работает.
- corepack enable и надежда на pnpm. На 24 experimental, с 25 убран. pnpm ставьте через mise.
- UFW allow 3000. Не открывайте. Node слушает localhost, снаружи — nginx на 80/443.
- Две копии node в PATH. type -a node покажет все. Первой должна быть shim mise. Если первой /usr/bin/node — снимите apt-пакет или почините порядок bashrc, новая сессия.
- npm установлен из apt отдельно от node. Пара node 24 из mise + npm из Ubuntu даёт непредсказуемые префиксы. Нужен только npm, который пришёл с node@lts.
FAQ
Почему нельзя поставить Node из apt на Ubuntu 24.04?
Пакет nodejs там 18.19.1, линия уже EOL. Для нового сервиса это неподдерживаемый рантайм. Нужен LTS 24 через mise use -g node@lts, сейчас это v24.21.0.
Нужен ли NodeSource, если есть mise?
Нет. NodeSource ставит node в систему и конфликтует с shims. Один менеджер версий — mise, один бинарь для systemd — путь из mise which.
Как поставить pnpm и не сломать это на Node 25?
Не через corepack: на Node 24 он experimental, в 25 его нет. Команда mise use -g pnpm ставит pnpm отдельно от версии Node.
Как позже обновить LTS, не переписывая гайд с нуля?
Снова mise use -g node@lts, проверка node -v, затем обновить ExecStart свежим mise which node и systemctl restart. Глобальный npm i -g для сервиса не используйте.
Почему юнит не видит node, хотя в SSH node -v работает?
У systemd нет вашего bashrc и activate. В ExecStart нужен абсолютный путь из mise which, User= — тот же человек, у которого ставили mise. Проверьте journalctl -u hello-node.