Как развернуть Node.js на VPS: Ubuntu, systemd и Nginx
Запускаем Node.js-приложение на Ubuntu 24.04: LTS-дистрибутив, отдельный пользователь, служба systemd, Nginx и проверка HTTP. С готовыми исходниками.

Развернём небольшое Node.js-приложение на Ubuntu 24.04: установим поддерживаемую ветку LTS, запустим процесс отдельным пользователем через systemd и подключим Nginx. После выхода из SSH приложение продолжит работать, а его порт будет доступен только на самом сервере.
Что будем устанавливать
Пример использует встроенный HTTP-модуль Node.js без сторонних npm-пакетов. Главная показывает страницу, /healthz возвращает JSON, неизвестные адреса получают 404. Так можно проверить весь путь запроса, прежде чем добавлять Express, базу данных или другую инфраструктуру.
Проверено 29 октября 2026 года на Ubuntu 24.04.5 LTS, архитектуре x86-64 и Node.js v26.13.0 из ветки 26 LTS. Номер фиксирует испытанное окружение. Перед новой установкой проверьте поддержку ветки и свежие исправления в официальном расписании Node.js.
Нужны доступ по SSH с sudo и свободный локальный порт 3000. На сервере будет создан отдельный проект /opt/hostfera-node. Для выбора ресурсов используйте руководство по первому VPS и подбор Hostfera.
Установите Node.js из официального дистрибутива
Команды рассчитаны на новый проект. Если указанные каталоги или пользователь уже существуют, сначала проверьте их назначение.
sudo -i
apt-get update
apt-get install -y ca-certificates curl xz-utils libatomic1 unzip nginx
VERSION=v26.13.0
case "$(uname -m)" in
x86_64) ARCH=x64 ;;
aarch64) ARCH=arm64 ;;
*) echo 'Проверьте поддержку архитектуры Node.js'; exit 1 ;;
esac
TEMP=$(mktemp -d)
cd "$TEMP"
curl --fail --show-error --location --proto '=https' \
"https://nodejs.org/dist/$VERSION/node-$VERSION-linux-$ARCH.tar.xz" \
-o "node-$VERSION-linux-$ARCH.tar.xz"
curl --fail --show-error --location --proto '=https' \
"https://nodejs.org/dist/$VERSION/SHASUMS256.txt" -o SHASUMS256.txt
grep " node-$VERSION-linux-$ARCH.tar.xz$" SHASUMS256.txt | sha256sum --check -
test ! -e /opt/hostfera-node/current || { echo 'Проект уже существует'; exit 1; }
install -d -m 755 /opt/hostfera-node/app
tar -xJf "node-$VERSION-linux-$ARCH.tar.xz" -C /opt/hostfera-node
ln -s "/opt/hostfera-node/node-$VERSION-linux-$ARCH" /opt/hostfera-node/current
/opt/hostfera-node/current/bin/node --version
В проверке SHA-256 должно быть OK. Архив и список сумм загружаются с официального HTTPS-сайта Node.js. Пакет libatomic1 включён по результатам испытания: без него запуск этого дистрибутива на стенде завершался ошибкой загрузки libatomic.so.1.
Для ARM выбран подходящий архив, но испытание этой инструкции выполнено на x86-64. При использовании пакетного менеджера или другого способа установки пути к Node.js могут отличаться.
Скачайте готовый пример
Скачать исходники Node.js-приложения. В ZIP находятся server.mjs, unit systemd и конфигурация Nginx; сам Node.js в него не включён.
curl --fail --show-error --location --proto '=https' \
https://hostfera.ru/static/downloads/hostfera-node-example-1.0.0.zip \
-o "$TEMP/node-example.zip"
printf '%s %s\n' '6728e99bb64b9c265979c4c952491d066e7bf220ab15e2af21d199b66bb50e36' "$TEMP/node-example.zip" | sha256sum --check -
unzip "$TEMP/node-example.zip" -d "$TEMP/example"
install -m 644 "$TEMP/example/hostfera-node-example/server.mjs" \
/opt/hostfera-node/app/server.mjs
/opt/hostfera-node/current/bin/node --check /opt/hostfera-node/app/server.mjs
Основной файл приложения:
import http from 'node:http';
const port = Number(process.env.PORT ?? 3000);
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
throw new Error('PORT must be an integer between 1024 and 65535');
}
const page = `<!doctype html><html lang="ru"><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Node.js на VPS — практика Hostfera</title>
<style>body{font:18px/1.6 system-ui;margin:0;background:#eef7f1;color:#142c20}main{max-width:760px;margin:9vh auto;padding:32px}small{color:#33714e}h1{font-size:clamp(32px,7vw,56px);line-height:1.1}section{padding:24px;border:1px solid #b6d5c0;border-radius:18px;background:#fff}a{color:#176335}code{overflow-wrap:anywhere}</style>
<main><small>HOSTFERA / ПРАКТИКА VPS</small><h1>Node.js работает<br>на вашем сервере.</h1>
<section><p>Приложение запущено отдельной службой systemd. Nginx принимает запрос и передаёт его на локальный порт.</p><p><a href="/healthz">Открыть проверку /healthz →</a></p><p>Файлы примера: <code>server.mjs</code> и отдельный unit-файл.</p></section></main></html>`;
const server = http.createServer((req, res) => {
const path = new URL(req.url, 'http://localhost').pathname;
res.setHeader('X-Content-Type-Options', 'nosniff');
if (!['GET', 'HEAD'].includes(req.method)) {
res.writeHead(405, { Allow: 'GET, HEAD' });
return res.end();
}
let status = 200;
let type = 'text/html; charset=utf-8';
let body = page;
if (path === '/healthz') {
type = 'application/json; charset=utf-8';
body = JSON.stringify({ status: 'ok', service: 'hostfera-node' });
} else if (path !== '/') {
status = 404;
type = 'text/plain; charset=utf-8';
body = 'Not found\n';
}
res.writeHead(status, { 'Content-Type': type, 'Content-Length': Buffer.byteLength(body) });
res.end(req.method === 'HEAD' ? undefined : body);
});
server.listen(port, '127.0.0.1', () => console.log(`Listening on 127.0.0.1:${port}`));
process.on('SIGTERM', () => {
server.close(() => process.exit(0));
setTimeout(() => process.exit(1), 5000).unref();
});
Адрес 127.0.0.1 в listen ограничивает входящие подключения локальным сервером. Запросы посетителей будет принимать Nginx. Для приложения на собственном фреймворке используйте его стартовый файл, зависимости и сборку; этот пример не выполняет сборку произвольного Next.js-проекта.
Создайте службу systemd
useradd --system --user-group --no-create-home --shell /usr/sbin/nologin hf_node
install -m 644 "$TEMP/example/hostfera-node-example/hostfera-node.service" \
/etc/systemd/system/hostfera-node.service
Содержимое unit-файла:
[Unit]
Description=Hostfera Node.js example
After=network.target
[Service]
Type=simple
User=hf_node
Group=hf_node
WorkingDirectory=/opt/hostfera-node/app
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/opt/hostfera-node/current/bin/node /opt/hostfera-node/app/server.mjs
Restart=on-failure
RestartSec=3
TimeoutStopSec=10
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
MemoryMax=256M
UMask=0027
[Install]
WantedBy=multi-user.target
Пути к коду и Node.js принадлежат root, а процесс работает как hf_node. Ограничение ProtectSystem=strict подходит этому примеру без записи файлов. Если ваше приложение сохраняет данные, создайте отдельный каталог, например в /var/lib/, назначьте ему владельца и добавьте разрешённый путь записи в unit. Не открывайте запись во весь /opt ради одной папки.
systemctl daemon-reload
systemctl enable --now hostfera-node.service
systemctl status hostfera-node.service --no-pager
curl --retry 5 --retry-connrefused --retry-delay 1 --fail http://127.0.0.1:3000/healthz
ss -ltnp 'sport = :3000'
Ожидаются состояние active, JSON со status: ok и слушающий адрес 127.0.0.1:3000. Если служба не стартовала, сначала посмотрите её журнал:
journalctl -u hostfera-node.service -n 50 --no-pager
Подключите Nginx
Скопируйте конфигурацию и измените server_name node.example.com на свой домен:
test ! -e /etc/nginx/sites-available/hostfera-node || { echo 'Файл уже существует'; exit 1; }
install -m 644 "$TEMP/example/hostfera-node-example/nginx.conf" \
/etc/nginx/sites-available/hostfera-node
server {
listen 80;
server_name node.example.com;
access_log /var/log/nginx/hostfera-node.access.log;
error_log /var/log/nginx/hostfera-node.error.log;
location ^~ /.well-known/acme-challenge/ {
root /var/www/hostfera-node-acme;
try_files $uri =404;
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
}
}
Затем включите виртуальный хост:
install -d -m 755 /var/www/hostfera-node-acme
ln -s /etc/nginx/sites-available/hostfera-node /etc/nginx/sites-enabled/hostfera-node
nginx -t
systemctl reload nginx
curl --resolve node.example.com:80:127.0.0.1 http://node.example.com/healthz
В последней команде используйте то же имя, которое записали в server_name. --resolve позволяет проверить виртуальный хост до изменения публичного DNS. Для внешнего доступа направьте A-запись домена на VPS и проверьте привязку домена.
Подключите HTTPS и проверьте полный путь
Настройте сертификат по инструкции Let's Encrypt. В этой конфигурации каталог для HTTP-01 — /var/www/hostfera-node-acme. При добавлении TLS сохраните location / с proxy_pass: Nginx должен продолжать передавать запросы приложению.
Снаружи должны открываться 80/443, а не порт 3000. Правила доступа разобраны в гайде UFW. После включения HTTPS проверьте главную, /healthz и несуществующий адрес; последний должен сохранить 404.
На стенде проверены приложение и Nginx на отдельном loopback-порту, доступном для браузера через SSH-туннель. Новый публичный домен и сертификат для этого примера не выпускались. Проверка не объявляет готовой инфраструктуру вашего будущего проекта.
Обслуживание и следующий шаг
Для перезапуска после изменения кода используйте systemctl restart hostfera-node.service, затем снова проверьте /healthz. Секреты и настройки окружения реального приложения храните отдельно от исходников и открытых архивов.
На стенде подтверждены GET, HEAD, настоящая 404, отказ неподдерживаемому методу и работа службы от отдельного пользователя. При остановке процесса Nginx выдаёт 502 — этот сценарий разобран в статье о диагностике 502 Bad Gateway.
Следующим этапом могут стать база данных, резервные копии и процесс выпуска новой версии. Начинайте с необходимых сервисов: установка Node.js сама по себе не требует Redis, Docker или нескольких процессов приложения.
База данных для приложения
Если проекту нужна реляционная база, используйте установку PostgreSQL на Ubuntu: отдельная роль, локальное подключение и проверка записи. Затем настройте копию и тест восстановления.
Первоисточники
Сообщите адрес статьи и фрагмент через контакты редакции.


