HostiServer
2026-03-23 12:40:00
Node.js на VPS: повний гайд від установки до production у 2026
Чому Node.js на VPS — і чому не shared hosting
Проблема в тому, що більшість гайдів в інтернеті закінчуються на node app.js — і все, ніби додаток готовий до production. Насправді між "запустив на localhost" та "додаток стабільно працює під навантаженням" — ціла прірва: процес-менеджер, reverse proxy, SSL, файрвол, моніторинг, захист від витоків пам'яті.
У цьому гайді — повний шлях від чистого Ubuntu-сервера до production-ready Node.js. Кожен крок з реальними конфігами та поясненнями чому саме так, а не інакше. Весь матеріал базується на рекомендаціях інженерів Hostiserver які щодня працюють з Node.js проєктами клієнтів.
Крок 1: Підготовка VPS
Перед встановленням Node.js — базове налаштування безпеки. Це займе 10 хвилин, але убереже від проблем потім. Свіжий Ubuntu VPS без цих кроків — як квартира з відчиненими дверима: рано чи пізно хтось зайде.
Щодо вибору конфігурації VPS: для невеликого API або бота достатньо 1 vCPU і 2 ГБ RAM. Для середнього проєкту з PM2 Cluster Mode, Redis та Nginx — рекомендуємо від 2 vCPU і 4 ГБ. NVMe диск обов'язковий якщо використовуєте Redis або маєте інтенсивне логування — різниця з HDD при випадковому читанні/записі колосальна.
Оновлення системи
Перше що робимо на будь-якому новому сервері — оновлюємо пакети. Стара версія OpenSSL чи ядра — це відомі вразливості які експлуатуються автоматичними сканерами:
sudo apt update && sudo apt upgrade -y
Створення окремого користувача
Ніколи не запускайте Node.js від root. Створіть окремого користувача з обмеженими правами:
sudo adduser nodeapp
sudo usermod -aG sudo nodeapp
su - nodeapp
Налаштування файрволу (UFW)
Ubuntu має вбудований UFW (Uncomplicated Firewall), але за замовчуванням він вимкнений. Включаємо і відкриваємо тільки три порти — SSH для управління, 80 для HTTP та 443 для HTTPS:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Після активації перевірте статус командою sudo ufw status — в списку повинні бути тільки ці три правила. Все інше заблоковано автоматично.
⚠️ Важливо: Не відкривайте порт 3000 (або інший порт додатку) назовні. Nginx буде проксувати трафік з 80/443 на ваш додаток. Прямий доступ до порту Node.js — це дірка в безпеці.
SSH-ключі замість паролів — обов'язково. Парольна автентифікація — одна з найпоширеніших причин зламу серверів через brute-force. Якщо ще не налаштували SSH-ключі — зробіть це першим ділом, навіть перед установкою Node.js. Згенеруйте пару ключів на локальній машині (ssh-keygen -t ed25519), скопіюйте публічний ключ на сервер (ssh-copy-id nodeapp@your-server-ip) та вимкніть парольну автентифікацію в /etc/ssh/sshd_config.
Крок 2: Встановлення Node.js через NVM
Забудьте apt install nodejs — ця команда дасть стару версію з репозиторію Ubuntu і створить конфлікти якщо на сервері більше одного проєкту. Проблема не тільки у версії: при оновленні через apt можуть зламатись глобальні npm-пакети, а відкотити зміни без знесення всього — складно.
Правильний спосіб — NVM (Node Version Manager). Він встановлюється у домашню директорію користувача, не потребує sudo, і дозволяє миттєво перемикатися між версіями для різних проєктів. Один VPS — два проєкти — один на Node 20, другий на Node 22 — жодних конфліктів.
Встановлення NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
Встановлення Node.js LTS
nvm install 22
nvm use 22
nvm alias default 22
У 2026 році золотий стандарт — LTS 22.x. Ця версія забезпечує баланс між новими можливостями (оновлений V8 engine, нативний Fetch API) та стабільністю для production.
Перевірка
node -v # v22.x.x
npm -v # 10.x.x
ℹ️ Альтернатива: Fast Node Manager (fnm) — швидша альтернатива NVM, написана на Rust. Обидва варіанти підходять для production. Головне — не використовуйте системний пакетний менеджер.
Крок 3: Деплой застосунку
Сервер готовий, Node.js встановлений — час деплоїти сам додаток. Найпростіший спосіб — через Git. Якщо у вас ще немає репозиторію — саме час створити, бо деплоїти через FTP чи scp окремі файли у 2026 році — це шлях до хаосу з версіями.
cd /home/nodeapp
git clone https://github.com/your-user/your-app.git
cd your-app
npm install --production
Прапорець --production ігнорує devDependencies — тестові фреймворки, лінтери та інструменти збірки не потрібні на production-сервері. Це економить місце та зменшує кількість потенційних вразливостей.
Якщо на VPS кілька проєктів — кожен в окремій директорії зі своїм ecosystem.config.js для PM2 та окремим конфігом Nginx. Наприклад: /home/nodeapp/api на порту 3000 і /home/nodeapp/admin на порту 3001, кожен за своїм Nginx server block.
Файл оточення (.env)
Ніколи не зберігайте паролі та ключі в коді. Створіть .env файл:
NODE_ENV=production
PORT=3000
DB_HOST=localhost
DB_PASSWORD=your_secure_password
SESSION_SECRET=random_string_here
🚨 Увага: Додайте .env у .gitignore. Якщо цей файл потрапить у Git-репозиторій — вважайте що всі паролі скомпрометовані.
Тестовий запуск
node app.js
Якщо додаток запустився без помилок і відповідає на curl http://localhost:3000 — переходимо до PM2.
Крок 4: PM2 для production
Запускати Node.js через node app.js на production — все одно що їхати без ременя безпеки. Процес впаде від першої необробленої помилки, і ніхто його не перезапустить.
PM2 — де-факто стандарт для управління Node.js процесами. Він перезапускає додаток при падінні, розподіляє навантаження по ядрах, і переживає перезавантаження сервера. Без PM2 будь-який необроблений exception зупинить ваш процес — і відновити його зможе тільки адміністратор вручну, якщо помітить проблему.
Встановлення
npm install -g pm2
ecosystem.config.js
Замість запуску через командний рядок — створіть конфігураційний файл:
module.exports = {
apps: [{
name: 'my-app',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
max_memory_restart: '1G',
exp_backoff_restart_delay: 100,
env_production: {
NODE_ENV: 'production',
PORT: 3000
}
}]
};
Що тут важливо:
- instances: 'max' — PM2 автоматично створить по одному процесу на кожне ядро CPU. На VPS з 4 ядрами — 4 процеси, кожен обробляє запити паралельно.
- exec_mode: 'cluster' — обов'язково. Без кластерного режиму Node.js використовує лише одне ядро, решта простоює.
- max_memory_restart: '1G' — захист від витоків пам'яті. Якщо процес з'їв більше 1 ГБ — PM2 тихо перезапустить його без простою (решта процесів продовжують працювати).
- exp_backoff_restart_delay — якщо додаток падає циклічно, затримка між рестартами збільшується поступово. Без цього PM2 може створити нескінченний цикл перезапусків.
Запуск та автостарт
pm2 start ecosystem.config.js --env production
pm2 save
pm2 startup
Тепер додаток переживе перезавантаження сервера і автоматично стартує.
Корисні команди PM2
pm2 list # Список процесів
pm2 logs my-app # Логи в реальному часі
pm2 monit # Моніторинг CPU/RAM
pm2 reload my-app # Zero-downtime перезапуск
Крок 5: Nginx як reverse proxy + SSL
Node.js не повинен слухати порт 80 або 443 напряму. Це одна з найпоширеніших помилок — "а навіщо Nginx, якщо Node.js сам вміє HTTP?" Відповідь: Nginx стоїть перед Node.js і виконує задачі які Node.js робить погано або не робить взагалі.
SSL-термінація — Nginx обробляє шифрування значно ефективніше. Захист від slow loris та інших повільних атак — Node.js витратить на кожне таке з'єднання цілий тред event loop, Nginx ні. Статичні файли — Nginx віддає їх напряму з диска, не навантажуючи Node.js. Балансування — якщо PM2 запустив 4 воркери, Nginx розподілить запити між ними.
Встановлення Nginx та Certbot
Nginx — найпопулярніший reverse proxy для Node.js. Certbot від Let's Encrypt видає безкоштовні SSL-сертифікати з автоматичним оновленням кожні 90 днів:
sudo apt install nginx certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com
Certbot сам додасть SSL-рядки в конфігурацію Nginx. Перевірити що автооновлення працює можна командою sudo certbot renew --dry-run.
Конфігурація Nginx
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
# Підтримка WebSockets
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
# Тайм-аути
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
💡 Порада: Зверніть увагу на рядки proxy_set_header Upgrade та Connection 'upgrade' — без них WebSocket-з'єднання (чати, нотифікації, real-time дашборди) не працюватимуть через Nginx.
Перевірка та перезапуск
sudo nginx -t
sudo systemctl reload nginx
Не забудьте також додати блок для перенаправлення HTTP → HTTPS:
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
Тепер ваш додаток доступний через HTTPS, а порт 3000 закритий файрволом від зовнішнього доступу. Замініть example.com на ваш реальний домен у всіх конфігах.
Якщо ваш додаток окрім API також віддає статичні файли (зображення, CSS, JavaScript для фронтенду) — додайте окремий location-блок щоб Nginx обслуговував їх напряму, не навантажуючи Node.js:
location /static/ {
alias /home/nodeapp/your-app/public/;
expires 30d;
add_header Cache-Control "public, immutable";
}
Це значно зменшить навантаження на Node.js процеси — Nginx віддає статику з диска в десятки разів ефективніше.
Безпека Node.js на production
Файрвол та SSH-ключі — це базовий рівень. Для Node.js є додаткові вектори атак, про які забувають. npm-екосистема — це тисячі залежностей, і кожна з них потенційна точка входу. А HTTP-заголовки за замовчуванням розкривають інформацію про ваш стек яка допомагає атакуючим.
Helmet.js
Набір middleware що виставляє безпечні HTTP-заголовки. Без Helmet ваш Express-застосунок відповідає з заголовками за замовчуванням які розкривають стек технологій (наприклад, X-Powered-By: Express) і не захищають від базових атак. Helmet за одну строчку коду закриває десяток поширених вразливостей — XSS, clickjacking, MIME sniffing:
const helmet = require('helmet');
app.use(helmet());
Rate Limiting
Обмежує кількість запитів з однієї IP-адреси за проміжок часу. Без rate limiting автоматичний скрипт може надіслати тисячі запитів за секунду — перебрати паролі, спарсити контент, або просто покласти ваш API надмірним навантаженням:
const rateLimit = require('express-rate-limit');
app.use(rateLimit({
windowMs: 15 * 60 * 1000, // 15 хвилин
max: 100 // максимум 100 запитів
}));
Для ендпоінтів авторизації встановіть окремий, жорсткіший ліміт — наприклад, 5 спроб за 15 хвилин. Для серйознішого захисту — додайте rate limiting ще й на рівні Nginx через директиву limit_req. Два рівні обмежень надійніше ніж один: Nginx відхиляє зайві запити ще до того як вони потрапляють до Node.js.
npm audit
Регулярно перевіряйте вразливості у залежностях:
npm audit
npm audit fix
Зробіть це частиною CI/CD або хоча б запускайте перед кожним деплоєм. Середній Node.js проєкт має сотні транзитивних залежностей — бібліотек які тягнуть інші бібліотеки, які тягнуть ще інші. Одна вразлива бібліотека десь на третьому рівні вкладеності може скомпрометувати весь сервер. За останні роки було кілька гучних інцидентів з npm-пакетами що крали змінні оточення — тобто паролі до баз даних та API-ключі.
⚠️ Нагадування: Node.js процес ніколи не повинен працювати від root. Окремий користувач з обмеженими правами — це не рекомендація, а вимога для production.
Оптимізація продуктивності та моніторинг
Додаток працює, Nginx проксує трафік, PM2 стежить за процесами — базова конфігурація готова. Тепер час зробити так, щоб все це працювало не просто правильно, а швидко. Три інструменти які дають найбільший приріст продуктивності: Redis для кешування, Gzip для стиснення відповідей, та правильний моніторинг щоб бачити де є вузькі місця.
Redis для кешування
Якщо ваш додаток часто ходить у базу даних з однаковими запитами — Redis кардинально змінює картину. У кейсі з eCommerce API зі вступу саме впровадження Redis для кешування важких SQL-запитів до каталогу товарів стало одним з трьох факторів що зменшили час відповіді з 850 до 120 мс.
Встановлення на Ubuntu:
sudo apt install redis-server -y
sudo systemctl enable redis-server
Принцип простий: перед запитом до бази — перевірити чи є результат в Redis. Якщо є — повернути з кешу (мікросекунди замість мілісекунд). Якщо немає — зробити запит, зберегти результат у кеш з TTL:
const Redis = require('ioredis');
const redis = new Redis();
async function getProducts(categoryId) {
const cacheKey = `products:${categoryId}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const products = await db.query(
'SELECT * FROM products WHERE category_id = ?',
[categoryId]
);
await redis.set(cacheKey, JSON.stringify(products), 'EX', 300);
return products;
}
TTL (Time To Live) залежить від типу даних: каталог товарів — 5-15 хвилин, конфігурації — 1 година, сесії користувачів — до 24 годин. Головне правило: кешуйте те що читається часто і змінюється рідко.
Один нюанс про який часто забувають — інвалідація кешу. Коли адміністратор оновлює ціну товару, старий кеш повинен бути видалений. Найпростіший підхід — при зміні даних видаляти відповідний ключ: await redis.del('products:' + categoryId). Наступний запит піде напряму в базу і оновить кеш свіжими даними.
Gzip стиснення через Nginx
Якщо ваш API повертає JSON — а більшість Node.js API саме так і роблять — Gzip стиснення зменшує обсяг трафіку в 5–10 разів. Це особливо помітно на мобільних з'єднаннях і при великих масивах даних. Додайте в секцію http головного конфігу Nginx (/etc/nginx/nginx.conf):
gzip on;
gzip_types application/json text/plain application/javascript;
gzip_min_length 1000;
Параметр gzip_min_length означає що Nginx не буде стискати відповіді менше 1000 байт — для дрібних відповідей overhead стиснення перевищує вигоду. Клієнти не помітять різниці — браузери та HTTP-бібліотеки автоматично розпаковують Gzip.
Zero-downtime деплой оновлень
Коли потрібно оновити код на production — не зупиняйте весь сервіс. PM2 у cluster mode підтримує команду reload, яка перезапускає процеси по одному. Поки один воркер перезавантажується з новим кодом, інші продовжують обробляти запити:
cd /home/nodeapp/your-app
git pull origin main
npm install --production
pm2 reload my-app
Користувачі нічого не помітять. Для більших проєктів варто налаштувати CI/CD пайплайн через GitHub Actions або GitLab CI — тоді деплой відбувається автоматично при пуші в main-гілку. Базовий workflow: push → автоматичний npm audit → npm test → SSH на VPS → git pull → npm install → pm2 reload. Весь цикл займає 30-60 секунд.
Моніторинг: Prometheus + Grafana
Додаток працює, але чи працює він добре? Без моніторингу ви дізнаєтесь про проблему тільки коли клієнти почнуть скаржитись — або коли PM2 перезапустить процес вже вп'яте за годину. Event Loop Lag поступово росте, пам'ять тече, з'єднання до бази не закриваються — все це невидимо без інструментів спостереження.
Найкраща зв'язка для production-моніторингу Node.js — Prometheus + Grafana. Prometheus збирає метрики з вашого додатку через спеціальний ендпоінт (використовуйте пакет prom-client), а Grafana візуалізує їх у дашборди з алертами на email або Slack. Ключові метрики:
| Метрика | Що показує | Коли бити тривогу |
|---|---|---|
| Event Loop Lag | Затримка обробки подій — найважливіша метрика для Node.js | > 100 мс |
| Process CPU | Навантаження на процесор кожним воркером | > 80% постійно |
| Heap Memory Used | Споживання пам'яті (шукайте тренд зростання — це memory leak) | Постійне зростання без падінь |
| Active Handles | Відкриті з'єднання, файли, таймери | Різке зростання |
ℹ️ Бюджетна альтернатива: Якщо Prometheus + Grafana — забагато для вашого проєкту, PM2 Plus (платна версія PM2) дає базовий моніторинг з веб-дашбордом без додаткового налаштування.
Production-чекліст: перевір перед запуском
Все налаштовано, додаток працює — але перш ніж пускати реальних користувачів, пройдіться по цьому списку. Кожен пропущений пункт — це потенційний інцидент о третій ночі. Краще витратити 15 хвилин на перевірку зараз, ніж годину на відновлення потім.
💡 Чекліст Node.js production:
☐ Node.js встановлений через NVM (не apt), версія LTS 22.x
☐ Додаток запущений через PM2 у cluster mode, не через node app.js
☐ PM2 налаштований на автостарт (pm2 save + pm2 startup)
☐ Nginx reverse proxy перед Node.js з SSL через Let's Encrypt
☐ Порт додатку (3000) закритий у файрволі, відкриті тільки 80, 443, SSH
☐ Процес працює від окремого користувача, не від root
☐ Паролі, ключі та токени у .env файлі, .env у .gitignore
☐ Helmet.js підключений для безпечних HTTP-заголовків
☐ Rate limiting налаштований (на рівні додатку та/або Nginx)
☐ npm audit не показує критичних вразливостей
☐ Логування працює — pm2 logs показує що відбувається
☐ Моніторинг налаштований — ви знаєте коли щось йде не так
Якщо всі пункти виконані — ваш Node.js додаток готовий до production навантаження. Якщо ні — кожен пропущений пункт це потенційний інцидент о 3 годині ночі.
5 помилок які ламають Node.js на production
Ці помилки повторюються з проєкту в проєкт. Кожна виглядає дрібницею на етапі розробки — але на production під навантаженням перетворюється на серйозну проблему з простоєм, витоком даних або деградацією продуктивності.
| Помилка | Наслідки | Рішення |
|---|---|---|
| Запуск від root | Вразливість у додатку = повний доступ до сервера | Окремий користувач з обмеженими правами |
| node app.js без PM2 | Одна помилка — додаток мертвий, ніхто не перезапустить | PM2 з cluster mode та auto-restart |
| Node.js слухає порт 80 напряму | Немає SSL, немає захисту від slow loris, немає балансування | Nginx reverse proxy перед Node.js |
| Паролі у коді або Git | Будь-хто з доступом до репозиторію бачить credentials | .env файл + .gitignore |
| Встановлення через apt install | Стара версія, конфлікти між проєктами, складне оновлення | NVM або fnm для управління версіями |
Всі ці помилки об'єднує одне: вони непомітні на етапі розробки і під час тестування. Проблеми починаються тільки коли додаток потрапляє під реальне навантаження або стає ціллю автоматичних сканерів — а це питання днів, не місяців.
🚀 Готові запустити Node.js на надійному VPS?
Продуктивність вашого додатку починається з правильного сервера. NVMe диски, гарантовані ресурси, підтримка яка розуміє Node.js.
💻 Cloud (VPS) Хостинг
- Від $19.95/міс — Починайте малим, масштабуйте миттєво
- KVM віртуалізація — Гарантовані ресурси без overselling
- NVMe сховище — Швидка продуктивність для Node.js
- Root доступ — Повний контроль: NVM, PM2, Nginx — все ваше
- 24/7 підтримка — <10 хв відповідь
🖥️ Виділені Сервери
- Від $200/міс — Для high-load Node.js API
- Кастомні конфігурації — Intel або AMD, до 128 ядер
- Кілька локацій — EU + USA
- 99.9% uptime — SLA гарантія
- DDoS захист — Включено
- Безкоштовна міграція — Допоможемо перенести проєкт
💬 Не впевнені який варіант вам необхідний?
💬 Напишіть нам і ми зі всім допоможемо!
Часті питання
- Яку версію Node.js обрати для production у 2026?
LTS 22.x — золотий стандарт. Ця версія отримує security-патчі до квітня 2027 і підтримує всі сучасні можливості: оновлений V8 engine, нативний Fetch API, покращений ESM. Встановлюйте через NVM, не через apt.
- Скільки оперативної пам'яті потрібно для Node.js на VPS?
Мінімум — 2 ГБ для невеликого додатку з PM2 та Nginx. Для API з Redis кешуванням та кількома воркерами — від 4 ГБ. Реальний eCommerce API стабільно працює на 4 ГБ з 4 PM2-процесами.
- PM2 чи systemd — що краще для Node.js?
PM2 — для Node.js він зручніший. Cluster mode з балансуванням, zero-downtime reload, вбудований моніторинг, ecosystem.config.js — все це з коробки. Systemd підходить для простих сервісів, але для Node.js PM2 дає більше контролю.
- Чи обов'язковий Nginx якщо Node.js сам може слухати порт 80?
Так, обов'язковий. Nginx обробляє SSL-термінацію, стиснення, статичні файли, та захищає від повільних з'єднань (slow loris). Node.js на порту 80 без Nginx — це production без захисту.
- Як оновити Node.js на production без простою?
Через NVM: встановіть нову версію (
nvm install 22.x), перевірте сумісність, потімpm2 reload all. PM2 у cluster mode перезапустить процеси по одному — жоден запит не буде втрачений.
- Чи потрібен Redis для кожного Node.js проєкту?
Ні, не для кожного. Redis дає максимальний ефект коли додаток часто робить однакові запити до бази даних — каталоги товарів, списки категорій, конфігурації. Для простого лендінгу або API з унікальними запитами на кожен виклик Redis не додасть помітної різниці.