cp.lv — VPS, выделенные и облачные серверы

cp.lv (LANGMARKET LLP) — хостинг-провайдер: VPS, выделенные и облачные серверы на NVMe с выбором ОС, полным root-доступом и поддержкой 24/7. Тест-период до оплаты: выделенные серверы — 5 дней, VPS и облако — 5 часов.

Contact Info
LANGMARKET LLP
Rīga, Latvija

+371 00 000 000

Follow Us

Личный кабинет

Язык

Nginx как reverse proxy для Node.js-приложения и systemd

Nginx как reverse proxy для Node.js-приложения и systemd

Запустить на сервере одной командой (сначала проверьте содержимое скрипта ниже):

curl -sSL https://cp.lv/scripts/nginx-reverse-proxy-nodejs.sh | sudo bash

…или через wget:

wget -qO- https://cp.lv/scripts/nginx-reverse-proxy-nodejs.sh | sudo bash

Содержимое скрипта nginx-reverse-proxy-nodejs.sh:

#!/usr/bin/env bash
# Nginx reverse proxy + systemd для Node.js-приложения (myapp)
set -euo pipefail

APP_NAME=myapp
APP_DIR=/var/www/myapp
APP_PORT=3000
NODE_BIN=$(command -v node)

# 1. systemd-юнит (переменные подставятся)
sudo tee /etc/systemd/system/${APP_NAME}.service > /dev/null << EOF
[Unit]
Description=Node.js ${APP_NAME}
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=${APP_DIR}
ExecStart=${NODE_BIN} app.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=${APP_PORT}

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now ${APP_NAME}

# 2. Nginx reverse proxy (кавычки 'EOF' — переменные Nginx не трогаем)
sudo tee /etc/nginx/sites-available/${APP_NAME} > /dev/null << 'EOF'
server {
    listen 80;
    server_name app.example.com;
    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 $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 60s;
    }
}
EOF

sudo ln -sf /etc/nginx/sites-available/${APP_NAME} /etc/nginx/sites-enabled/${APP_NAME}
sudo nginx -t && sudo systemctl reload nginx
echo "Готово: ${APP_NAME} проксируется через Nginx на порт ${APP_PORT}."

Генератор systemd-юнита

Заполните поля — получите готовый файл .service для автозапуска сервиса.

Файл myapp.service — сохраните в /etc/systemd/system/:

Установка одной командой:

Node.js-приложение обычно слушает высокий порт вроде 3000, но выставлять его наружу не стоит. Правильная схема — Nginx перед приложением как reverse proxy: он принимает запросы на 80/443, отдаёт статику, терминирует HTTPS и проксирует динамику в Node.js. Ниже — конфиг Nginx и systemd-юнит для автозапуска приложения.

Зачем reverse proxy перед Node.js

  • HTTPS терминируется на Nginx, приложению не нужно возиться с сертификатами.
  • Порт 80/443 — стандартные порты, а Node.js остаётся на localhost:3000.
  • Статика отдаётся Nginx напрямую, быстрее и дешевле по CPU.
  • Несколько приложений живут за одним IP по разным доменам.

Шаг 1. Приложение слушает только localhost

Убедитесь, что Node.js биндится на 127.0.0.1, а не на 0.0.0.0:

app.listen(3000, '127.0.0.1', () => {
  console.log('server on 127.0.0.1:3000');
});

Тогда напрямую снаружи порт недоступен — только через Nginx.

Шаг 2. systemd-юнит для приложения

Вместо ручного запуска оформите приложение как сервис systemd. Откройте файл в любом консольном редакторе (nano, vim или mcedit) и вставьте содержимое:

sudo nano /etc/systemd/system/myapp.service

Содержимое файла:

[Unit]
Description=Node.js myapp
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/home/deploy/.nvm/versions/node/v20.11.0/bin/node app.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3000

[Install]
WantedBy=multi-user.target

Либо создайте файл целиком одной командой — скопируйте и вставьте в консоль (heredoc запишет весь файл сразу):

sudo tee /etc/systemd/system/myapp.service > /dev/null << 'EOF'
[Unit]
Description=Node.js myapp
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/home/deploy/.nvm/versions/node/v20.11.0/bin/node app.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3000

[Install]
WantedBy=multi-user.target
EOF

Укажите полный путь к бинарнику node (найдите его командой which node). Включите и запустите сервис:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp

Логи приложения теперь в journald:

sudo journalctl -u myapp -f

Шаг 3. Конфиг Nginx reverse proxy

Откройте конфиг в редакторе (sudo nano /etc/nginx/sites-available/myapp) и вставьте содержимое:

server {
    listen 80;
    server_name app.example.com;

    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 $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # поддержка WebSocket
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_read_timeout 60s;
    }
}

Или создайте конфиг целиком одной командой (кавычки 'EOF' важны — переменные Nginx $host останутся как есть):

sudo tee /etc/nginx/sites-available/myapp > /dev/null << 'EOF'
server {
    listen 80;
    server_name app.example.com;
    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 $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 60s;
    }
}
EOF

Заголовки X-Forwarded-* передают приложению реальный IP клиента и протокол. Блок Upgrade/Connection нужен для WebSocket (socket.io и т.п.).

Шаг 4. Активация и HTTPS

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Добавьте бесплатный сертификат Let’s Encrypt — certbot сам пропишет 443 и редирект:

sudo certbot --nginx -d app.example.com

Шаг 5. Отдача статики через Nginx

Чтобы не гонять статику через Node.js, отдавайте её напрямую. Добавьте в server block:

location /static/ {
    alias /var/www/myapp/public/;
    expires 30d;
    access_log off;
}

Диагностика

Симптом Причина Проверка
502 Bad Gateway приложение не отвечает systemctl status myapp
504 Gateway Timeout долгий ответ Node увеличьте proxy_read_timeout
WebSocket рвётся нет заголовков Upgrade добавьте блок Upgrade/Connection
Клиент видит IP сервера нет X-Real-IP проверьте proxy_set_header

Частые вопросы

Почему Nginx возвращает 502 при работающем приложении? Nginx не может достучаться до proxy_pass. Проверьте, что приложение слушает именно 127.0.0.1:3000 и сервис запущен: journalctl -u myapp.

Нужен ли pm2, если есть systemd? Нет, это взаимозаменяемые способы держать процесс живым. systemd — системный и не требует дополнительных пакетов; pm2 удобнее для кластера и встроенного мониторинга. Выбирайте один.

Как передать реальный IP клиента в приложение? Через заголовок X-Real-IP / X-Forwarded-For. В приложении включите доверие прокси (в Express — app.set('trust proxy', 1)).

Как проксировать WebSocket? Добавьте proxy_http_version 1.1 и заголовки Upgrade/Connection "upgrade" в блок location, иначе соединение будет обрываться.

Схема Nginx + Node.js под systemd — надёжная основа для продакшена. Подобрать сервер под неё можно на странице аренда VPS, а для контейнерного деплоя пригодится VPS для Docker.

Кристапс Берзиньш

Кристапс Берзиньш

CEO, основатель. Отвечает за стратегию cp.lv и следит, чтобы хостинг оставался честным и понятным для клиента.