Антон Ларичев

Введение
Node.js отлично справляется с обработкой запросов, но запускать его в продакшене напрямую — плохая идея. Прямой запуск на 80 или 443 порту требует прав root, не даёт нормально терминировать SSL, отдавать статику и балансировать нагрузку между несколькими инстансами. Решение — поставить перед Node.js обратный прокси-сервер Nginx.
Nginx возьмёт на себя SSL, gzip-сжатие, отдачу статики, лимиты соединений и кэширование. А приложение на Node.js сосредоточится только на бизнес-логике. В этой статье разберём базовую настройку, балансировку нагрузки, проксирование WebSocket и типичные грабли.
Зачем вообще нужен reverse proxy
Reverse proxy принимает запросы от клиентов и перенаправляет их на внутренние сервисы. В отличие от обычного прокси он защищает не клиента, а сервер. Преимущества такого подхода:
- Один публичный порт (443) для нескольких приложений
- SSL-терминация в одном месте
- Балансировка нагрузки между инстансами Node.js
- Защита от медленных клиентов (slowloris-атаки)
- Кэширование и отдача статики на уровне C, а не JavaScript
Node.js при этом слушает локальный порт вроде 3000, и снаружи он недоступен.
Установка и базовая конфигурация
Ставим Nginx на Ubuntu или Debian:
sudo apt update
sudo apt install nginx
sudo systemctl enable --now nginx
Конфиги сайтов лежат в /etc/nginx/sites-available/, а активируются симлинком в /etc/nginx/sites-enabled/. Создаём файл /etc/nginx/sites-available/app.conf:
server {
listen 80;
server_name example.com www.example.com;
location / {
# Проксируем все запросы на Node.js, который слушает localhost:3000
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;
# Таймауты на чтение и подключение к upstream
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
}
}
Активируем сайт и проверяем конфигурацию:
sudo ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Команда nginx -t проверит синтаксис до перезагрузки — это спасёт от падения сервера в проде.
Доверие к заголовкам в Express
Если в приложении используется Express, нужно сказать ему доверять прокси, иначе req.ip будет возвращать 127.0.0.1:
const express = require('express');
const app = express();
// Доверяем первому прокси — Nginx на той же машине
app.set('trust proxy', 'loopback');
app.get('/', (req, res) => {
// Теперь здесь будет реальный IP клиента из X-Forwarded-For
res.send(`Ваш IP: ${req.ip}`);
});
app.listen(3000, '127.0.0.1');
Обратите внимание на 127.0.0.1 в listen — приложение принципиально не слушает внешний интерфейс.
SSL через Let's Encrypt
Для HTTPS используем certbot — он сам выпустит сертификат и поправит конфиг Nginx:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Certbot добавит блок listen 443 ssl и редирект с HTTP на HTTPS. Автообновление настраивается через systemd-таймер, ничего делать не надо.
Балансировка нескольких инстансов
Node.js однопоточный, поэтому для использования всех ядер запускают несколько процессов на разных портах. Nginx распределит между ними нагрузку через директиву upstream:
upstream node_backend {
# least_conn отправит запрос на инстанс с минимальным числом активных соединений
least_conn;
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 max_fails=3 fail_timeout=30s;
# Держим до 32 keepalive-соединений к каждому бэкенду
keepalive 32;
}
server {
listen 443 ssl http2;
server_name example.com;
location / {
proxy_pass http://node_backend;
proxy_http_version 1.1;
# Пустой Connection нужен для работы keepalive с upstream
proxy_set_header Connection "";
proxy_set_header Host $host;
}
}
Алгоритм least_conn лучше дефолтного round-robin для долгих запросов. Если у вас сессии в памяти процесса — добавьте ip_hash, чтобы клиент всегда попадал на тот же инстанс.
Отдача статики и WebSocket
Node.js не должен отдавать CSS, JS и картинки — это работа Nginx:
location /static/ {
alias /var/www/app/public/;
# Кэшируем статику в браузере на год
expires 1y;
add_header Cache-Control "public, immutable";
}
location /ws {
proxy_pass http://node_backend;
proxy_http_version 1.1;
# Обязательные заголовки для апгрейда соединения до WebSocket
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# WebSocket может висеть долго — увеличиваем таймаут
proxy_read_timeout 3600s;
}
Частые ошибки
502 Bad Gateway. Nginx не может достучаться до upstream. Проверьте, что Node.js действительно запущен и слушает указанный порт: ss -tlnp | grep 3000. Часто причина в SELinux или AppArmor, которые блокируют исходящие соединения от Nginx.
Реальный IP теряется. Без X-Forwarded-For и trust proxy приложение видит только IP Nginx. Это ломает rate limiting и аналитику.
WebSocket рвётся через минуту. Дефолтный proxy_read_timeout — 60 секунд. Если соединение неактивно, Nginx его закроет. Поднимайте таймаут или настройте ping-pong со стороны приложения.
Большие загрузки падают с 413. Лимит на размер тела запроса по умолчанию 1 МБ. Добавьте client_max_body_size 50M; в нужный server или location.
Бесконечный редирект на HTTPS. Если за Nginx стоит ещё один прокси или CDN, приложение может не понимать, что соединение уже зашифровано. Проверяйте X-Forwarded-Proto и доверяйте ему в коде.
Утечка соединений к upstream. Без keepalive в upstream и пустого заголовка Connection Nginx открывает новое TCP-соединение на каждый запрос. На высокой нагрузке это съедает порты.
Заключение
Nginx перед Node.js — стандарт для продакшен-деплоя. Получасовая настройка даёт SSL, балансировку, защиту от примитивных атак и быструю отдачу статики практически бесплатно по ресурсам. Главное — не забыть про trust proxy в приложении, корректно прокинуть заголовки и поднять таймауты для WebSocket. Дальше можно идти в сторону кэширования ответов API через proxy_cache, rate limiting через limit_req_zone и вынесения Nginx на отдельную машину перед кластером бэкендов.






Комментарии
0