Вся инфраструктура современного интернета построена на стеке протоколов TCP/IP (Transmission Control Protocol / Internet Protocol). Когда пользователь вводит адрес сайта в браузере или мобильное приложение отправляет запрос к REST API, между клиентом и сервером разворачивается многоуровневая цепочка взаимодействия: преобразование доменного имени через DNS, установление надежного транспортного соединения TCP и передача прикладных данных протокола HTTP/HTTPS.
В операционных системах Linux сетевое взаимодействие представляется абстракцией сетевого сокета (Network Socket). Сетевой сокет идентифицируется кортежем из пяти элементов (5-tuple):
{Протокол (TCP/UDP), IP-адрес источника, Порт источника, IP-адрес назначения, Порт назначения}
Сетевой порт представляет собой 16-битное числовое значение в диапазоне от 0 до 65535. Согласно классификации IANA, порты разделены на три диапазона:
80 для HTTP, 443 для HTTPS, 22 для SSH, 53 для DNS). В Linux bind на эти порты исторически разрешен исключительно процессам с привилегиями суперпользователя root (или с capability CAP_NET_BIND_SERVICE).3000 для Node.js / Next.js, 5432 для PostgreSQL, 6379 для Redis, 8080 для альтернативных прокси).В производственной среде веб-приложение Node.js практически никогда не выставляется напрямую в глобальный интернет на 80 или 443 порт. Между внешними пользователями и бэкенд-сервисом устанавливается промежуточный защитный слой — обратный прокси-сервер (Reverse Proxy), в качестве которого обычно выступает Nginx, Caddy или Envoy.
Обычный прямой прокси (Forward Proxy) скрывает клиентов от внешних серверов (например, корпоративный прокси для выхода в интернет). Обратный прокси (Reverse Proxy) защищает внутренние серверы от внешних клиентов: для внешнего мира Nginx является единственной точкой входа, принимающей клиентские TCP-соединения, а запросы к Node.js пересылаются по быстрой локальной сети (Upstream).
+-------------------------------------------------------------------------+
| АРХИТЕКТУРА ВЕБ-ТРАФИКА И REVERSE PROXY |
+-------------------------------------------------------------------------+
| КЛИЕНТ (Браузер) |
| 1. Резолвинг DNS: api.company.com ──► 198.51.100.10 |
| 2. HTTPS Handshake (TLS 1.3) к порту 443 |
| │ |
| ▼ |
| ПУБЛИЧНЫЙ СЕРВЕР LINUX (198.51.100.10) |
| ┌───────────────────────────────────────────────────────────────────┐ |
| │ NGINX REVERSE PROXY (Порт 80 / 443) │ |
| │ • SSL/TLS Termination (расшифровка трафика аппаратно) │ |
| │ • Отдача статики (изображения, JS-бандлы) через sendfile() │ |
| │ • Защита от Slowloris и буферизация медленных клиентов │ |
| │ • Gzip / Brotli сжатие ответов │ |
| │ • Защита от DDoS (Rate Limiting) │ |
| └────────────────────────────┬──────────────────────────────────────┘ |
| │ Быстрый локальный HTTP/1.1 |
| ▼ (Unix Socket или 127.0.0.1:3000) |
| ┌───────────────────────────────────────────────────────────────────┐ |
| │ NODE.JS / NEXT.JS RUNTIME (Непривилегированный порт 3000) │ |
| │ • Пользователь: 'node' (UID 1001, без прав root) │ |
| │ • Чистая бизнес-логика (API, рендеринг страниц, запросы к БД) │ |
| └───────────────────────────────────────────────────────────────────┘ |
+-------------------------------------------------------------------------+Клиент проверяет локальный кэш, файл /etc/hosts и запрашивает DNS-резолвер провайдера для получения IP-адреса хоста.
Браузер выполняет тройное рукопожатие SYN/ACK с портом 443 Nginx и согласовывает криптографические ключи шифрования TLS 1.3.
Nginx проверяет лимиты частоты запросов (rate limiting), валидирует размер тела запроса (client_max_body_size) и блокирует аномалии.
Nginx пересылает запрос на http://127.0.0.1:3000, добавляя служебные заголовки с реальным IP клиента (X-Forwarded-For).
Node.js исполняет роут, возвращает JSON. Nginx сжимает его алгоритмом Gzip/Brotli и передает клиенту по защищенному каналу.
Почему в промышленной эксплуатации считается грубой архитектурной ошибкой запускать процесс Node.js напрямую на портах 80 и 443 от имени суперпользователя root?
nginx -s reload, которая перечитывает конфигурацию и плавно переключает трафик на новую версию контейнера без сброса активных клиентских TCP-соединений.sendfile(): передача картинок, шрифтов и JS-скриптов напрямую из файловой системы в сетевую карту на уровне ядра Linux (Zero-Copy), разгружая память и Event Loop Node.js.limit_req) для защиты эндпоинтов авторизации от перебора паролей.Типичный конфигурационный файл виртуального хоста /etc/nginx/sites-available/app.conf реализует перенаправление HTTP на HTTPS и проксирование на порт приложения:
# Определение пула бэкенд-серверов (Upstream)
upstream nodejs_backend {
# Подключение к локальному приложению Node.js
server 127.0.0.1:3000 max_fails=3 fail_timeout=10s;
keepalive 32; # Пул постоянных соединений с бэкендом
}
# 1. Редирект всего незашифрованного HTTP трафика на HTTPS
server {
listen 80;
listen [::]:80;
server_name api.example.com;
# Сертификация Let's Encrypt через ACME Challenge
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# 2. Основной виртуальный хост HTTPS
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name api.example.com;
# Пути к сертификатам безопасности
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# Современные стойкие криптографические протоколы
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Буферизация и защита от медленных клиентов (Slowloris)
client_body_buffer_size 128k;
client_max_body_size 10M;
client_body_timeout 12s;
client_header_timeout 12s;
# Сжатие ответов на лету
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1024;
# Проксирование запросов к Node.js
location / {
proxy_pass http://nodejs_backend;
proxy_http_version 1.1;
# Поддержка постоянных соединений и WebSockets
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Передача реальных сетевых метаданных клиента в Node.js
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_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}Когда запрос проходит через Nginx, свойство req.socket.remoteAddress в Node.js будет содержать 127.0.0.1 (IP-адрес самого Nginx). Чтобы получить настоящий IP пользователя, приложение обязано извлечь заголовок x-forwarded-for:
import http from 'node:http';
const server = http.createServer((req, res) => {
// Заголовок X-Forwarded-For формируется в виде цепочки: "client_ip, proxy1_ip, proxy2_ip"
const forwardedHeader = req.headers['x-forwarded-for'];
let clientIp: string;
if (typeof forwardedHeader === 'string') {
// Первый адрес в списке — исходный IP клиента
clientIp = forwardedHeader.split(',')[0].trim();
} else {
// Резервное значение (если запрос пришел напрямую в обход прокси)
clientIp = req.socket.remoteAddress ?? 'unknown';
}
const clientProto = req.headers['x-forwarded-proto'] ?? 'http';
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
message: 'Запрос успешно обработан',
clientIp,
protocol: clientProto,
host: req.headers['host'],
}));
});
server.listen(3000, () => {
console.log('[Node] Внутренний микросервис ожидает трафик от Nginx на порту 3000');
});Ядро Linux передает событие нового сетевого соединения воркерам Nginx через неблокирующий механизм epoll.
Один воркер Nginx способен удерживать десятки тысяч одновременных открытых соединений.
Nginx расшифровывает TLS-пакеты, сверяет поле Server Name Indication (SNI) и выбирает нужный блок server_name.
Криптографические вычисления выполняются в оптимизированных C-библиотеках OpenSSL.
Проверка частоты запросов в разделяемой памяти. Тело запроса считывается в буфер оперативной памяти.
Если клиент медленно шлет байты (Slowloris), Node.js не тратит процессорные тики и ждет полный пакет.
Nginx открывает быстрый локальный сокет к Node.js на порту 3000 и мгновенно передает собранный HTTP-запрос.
Передача метаданных осуществляется через стандартизированные заголовки X-Forwarded-*.
Почему бэкенд-фреймворк (например, Express или Fastify) требует явного включения настройки `trust proxy = true` при работе за Nginx?
Если в конфигурации Nginx внутри блока location забыть директиву:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;все пользователи вашего веб-сервиса в логах бэкенда и системах антифрода будут иметь один и тот же IP-адрес 127.0.0.1. Это приведет к блокировке всех легитимных клиентов при попытке ограничить доступ одному спамеру.
Разница между proxy_pass http://localhost:3000; и proxy_pass http://localhost:3000/; колоссальна:
# ВАРИАНТ А: запрос /api/users пересылается в Node.js как /api/users
location /api/ {
proxy_pass http://127.0.0.1:3000;
}
# ВАРИАНТ Б (со слэшем): Nginx обрежет префикс /api/ и отправит запрос как /users!
location /api/ {
proxy_pass http://127.0.0.1:3000/;
}Случайное добавление косой черты в конце адреса upstream ломает маршрутизацию API-роутов.
При отключении буферизации Nginx начинает передавать ответ от Node.js клиенту синхронно, байт за байтом. Если пользователь находится на мобильном 3G-интернете с высокой задержкой, сокет Node.js будет оставаться занятым десятки секунд, пока медленный клиент выкачивает ответ, что приводит к исчерпанию пула соединений бэкенда.
Стандартный метод dns.lookup в Node.js использует системный вызов getaddrinfo(), который не учитывает DNS TTL и может кэшировать IP-адреса навсегда в рамках процесса. При миграции базы данных или смене IP облачного сервиса Node.js продолжит отправлять трафик на старый, выключенный сервер.
Решение: В микросервисных архитектурах используйте dns.resolve4 или HTTP-агенты с поддержкой ротации DNS.