OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

Node.js keepalive и http.Agent: практическая настройка с Nginx и upstream-пулами

Разбираем пул http.Agent в Node.js и практику keepalive: какие параметры важны (maxSockets, freeSocketTimeout, socketActiveTTL), как согласовать их с Nginx (upstream keepalive) и сократить TIME_WAIT. Дадим пресеты и чек‑лист диагностики.
Node.js keepalive и http.Agent: практическая настройка с Nginx и upstream-пулами

Если ваши Node.js-сервисы ходят к внешним HTTP-API, базам через HTTP-прокси или за Nginx выступают как upstream, от корректной настройки keepalive зависит не только пиковая производительность, но и стабильность: длина очередей, число сокетов в TIME_WAIT, расход CPU на установку TCP-сессий и предсказуемость латентности. В этой статье разберем, как устроен пул соединений http.Agent в Node.js, какие параметры реально влияют на поведение (keepAlive, keepAliveMsecs, maxSockets, maxFreeSockets, freeSocketTimeout, socketActiveTTL), и как согласовать их с настройками Nginx (upstream keepalive, proxy_http_version, keepalive_timeout, keepalive_requests). Отдельно обсудим, как избежать шторма TIME_WAIT и что не стоит «лечить» на уровне sysctl.

Почему keepalive критичен для Node.js-клиента и Nginx

HTTP/1.1 по умолчанию поддерживает постоянные соединения, но в реальных приложениях они часто не используются из‑за неверных заголовков (Connection: close), несогласованных тайм-аутов и отсутствия пула в клиенте. В результате каждое обращение — новая TCP-сессия и новый TLS-хендшейк. Это повышает латентность и нагружает ядро, провоцируя лавину соединений и рост TIME_WAIT на стороне инициатора (как правило, Node.js-процесса).

Правильно настроенный keepalive решает эту проблему: соединения к одному и тому же хосту/порту переиспользуются, агент контролирует их количество и «жизненный цикл», а Nginx держит теплый пул к upstream-узлам, ускоряя проксирование и снижая системные накладные расходы.

Коротко: без пула вы упираетесь в лишние TCP/TLS-хендшейки и time_wait; с корректным keepalive — в пропускную способность приложения и сети, что гораздо предсказуемее и дешевле.

Как устроен http.Agent в Node.js

http.Agent — это пул TCP-соединений для HTTP/1.1. Он сопоставляет ключам (комбинация протокол/хост/порт) набор открытых сокетов, распределяет запросы по активным и свободным соединениям, и соблюдает лимиты. Важные моменты:

  • По умолчанию Agent переиспользует соединения только если включен keepAlive.
  • maxSockets ограничивает одновременное число активных сокетов на один origin. Это главный «предохранитель» от перегрузки upstream-а и вашей машины.
  • maxFreeSockets ограничивает размер пула свободных (idle) сокетов для повторного использования.
  • freeSocketTimeout управляет, как долго свободный сокет будет лежать в пуле, пока его не закроют.
  • keepAliveMsecs — периодические TCP keepalive-пробы на соединении (на практике влияет мало на HTTP-уровень, но полезно для обнаружения «мертвых» peer-ов).
  • socketActiveTTL — максимальный «возраст» активного сокета; по истечении TTL соединение не переиспользуется, что помогает распределить нагрузку и обновлять TLS/маршруты.
  • В новых версиях есть scheduling (lifo или fifo) — стратегия выбора свободного сокета из пула.

Клиентские библиотеки вроде axios, got и node-fetch (в Node 18+ это реализация на базе undici) либо проксируют в http.Agent, либо имеют собственные «диспетчеры». Для axios вы явно передаете httpAgent/httpsAgent; для fetch/undici — создаете Agent или Pool и пробрасываете его как dispatcher.

Схема пула соединений http.Agent и переиспользования keepalive через Nginx

Ключевые параметры http.Agent: практические рекомендации

Значения по умолчанию в Node.js часто не совпадают с вашими целями по пропускной способности и стабильности. Практический минимум, с которого стоит начинать:

  • keepAlive: true — обязательно. Без этого пула фактически нет.
  • maxSockets: подбирается по нагрузке и возможностям upstream-а. Часто разумно ставить 50–200 на origin для API и до 500–1000 для внутренних шина-подобных сервисов с низкой латентностью. Если у вас несколько воркеров, умножайте на их число.
  • maxFreeSockets: 1/4–1/2 от maxSockets обычно достаточно, чтобы держать «теплые» соединения и не тратить память зря.
  • freeSocketTimeout (мс): подстраивайте под таймаут keepalive на стороне сервера/прокси (например, Nginx keepalive_timeout и upstream keepalive_timeout). Делайте немного меньше серверного значения, чтобы клиент закрывал соединение первым и не ловил RST на повторном использовании.
  • socketActiveTTL: 30–120 секунд — хороший диапазон, если вы хотите постоянное «освежение» соединений и избегаете долгоживущих TCP-путей.
  • scheduling: lifo помогает держать сокеты «горячими» (используются самые свежие), что может уменьшить вероятность таймаутов.

Пример базовой конфигурации агента для axios:

const http = require('http');
const https = require('https');
const axios = require('axios');

const httpAgent = new http.Agent({
  keepAlive: true,
  maxSockets: 200,
  maxFreeSockets: 100,
  freeSocketTimeout: 15000,
  keepAliveMsecs: 1000,
  socketActiveTTL: 60000,
  scheduling: 'lifo'
});

const httpsAgent = new https.Agent({
  keepAlive: true,
  maxSockets: 200,
  maxFreeSockets: 100,
  freeSocketTimeout: 15000,
  keepAliveMsecs: 1000,
  socketActiveTTL: 60000,
  scheduling: 'lifo'
});

const api = axios.create({
  httpAgent,
  httpsAgent,
  timeout: 10000
});

Если используете встроенный fetch (Node 18+), рассмотрите undici и его Agent/Pool с аналогичной логикой лимитов и keepalive.

Nginx как reverse proxy к Node.js: согласуем keepalive

Теперь сторона прокси. Задача Nginx — эффективно обслуживать клиентские соединения и держать пул подключений к Node.js. Ошибки здесь столь же болезненны, как и на стороне Node. Если вы планируете выделять инфраструктуру под бэкенды, удобно запускать приложение на облачном VDS — вы контролируете ядро, сетевой стек и лимиты.

Клиентский keepalive

Для клиентов (браузеров, внешних сервисов) важны keepalive_timeout и keepalive_requests. Не делайте таймаут слишком коротким — иначе браузеры не успеют переиспользовать соединение. 30–65 секунд — разумный выбор для публичных сайтов. Ограничение keepalive_requests позволяет время от времени «пересядать» на новое соединение, что полезно для обновления TLS и снижения риска накопления ошибок.

http {
  keepalive_timeout 60s;
  keepalive_requests 1000;
}

Upstream keepalive к Node.js

Чтобы Nginx переиспользовал соединения к вашему Node.js-приложению, включаем upstream keepalive и говорим прокси работать по HTTP/1.1. Важно также правильно обработать заголовок Connection.

upstream node_backend {
  server 127.0.0.1:3000;
  keepalive 128;                 # количество idle соединений на воркер
  keepalive_requests 10000;      # сколько запросов в рамках одного соединения
  keepalive_timeout 30s;         # закрывать простаивающие соединения спустя 30s
}

server {
  listen 80;

  location / {
    proxy_http_version 1.1;

    # Не заставлять закрывать соединение к upstream
    proxy_set_header Connection "";

    # Обязательные заголовки
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_pass http://node_backend;
  }
}

Почему proxy_set_header Connection ""? Передача Connection: keep-alive upstream-у не нужна, а иногда вредна: Nginx сам управляет переиспользованием, а заголовок «Connection» — хоп-бай-хоп. Пустое значение гарантирует, что вы не навяжете upstream-у нежелательное поведение. Для WebSocket/Upgrade соединений используйте условную логику, например через map, чтобы не конфликтовать с обычными запросами.

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''       '';
}

server {
  location /ws {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_pass http://node_backend;
  }
}

TIME_WAIT и исчерпание эфемерных портов

Большое число коротких HTTP-запросов без keepalive приводит к лавине TIME_WAIT у инициатора соединений (клиент). На Linux это быстро упирается в пул эфемерных портов и вызывает всплеск ошибок подключения и таймаутов. Типичные «обходные пути» через sysctl (tcp_tw_reuse, агрессивное снижение tcp_fin_timeout) дают сомнительную стабильность и непредсказуемые побочные эффекты.

Правильное решение — соединения надо переиспользовать. Включайте keepAlive в Node.js-агенте, подбирайте maxSockets под пропускную способность upstream-а, увеличивайте пул idle-сокетов до разумного уровня и согласуйте таймауты с Nginx. Это уменьшит число новых TCP-сессий и резко сократит TIME_WAIT.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Согласование таймаутов Node.js и Nginx

Частая причина загадочных ECONNRESET и «висящих» запросов — разъехавшиеся таймауты. Правило простое: таймаут простоя в клиенте (Node.js freeSocketTimeout) должен быть немного меньше серверного (keepalive_timeout у Nginx для клиента или upstream keepalive_timeout для соединений к backend-у). Тогда соединение закрывает именно инициатор, а не сервер внезапным RST посреди вашего нового запроса.

Если Node.js выступает сервером, имеет смысл отрегулировать и server.keepAliveTimeout на стороне приложения, чтобы он не противоречил Nginx. Для чистоты измерений сведите все значения в читаемую таблицу таймаутов и лимитов. Если вы обслуживаете HTTPS, позаботьтесь о корректных сертификатах — это снижает лишние renegotiation и ошибки рукопожатия. Для выпуска поможет раздел «SSL-сертификаты».

Тонкая настройка: очереди, backpressure и retries

Один только maxSockets не решит всех проблем. При бурстовой нагрузке запросы начнут стоять в очереди на свободный сокет. Это нормально — так вы защищаете upstream от перегрузки. Главное — наблюдать метрики очередей и своевременно масштабировать инстансы, а не бездумно увеличивать maxSockets до бесконечности.

Если у вас есть ретраи, обязательно делайте их идемпотентными и с экспоненциальной паузой. В противном случае при кратковременном спазме сеть/бэкенд вы получите «шторм повторов» и еще больший рост очередей и латентности.

Рецепт пресетов для разных профилей

Внешнее API с умеренной латентностью

Частые короткие запросы к одному хосту:

  • Node.js http.Agent: maxSockets: 100, maxFreeSockets: 50, freeSocketTimeout: 15000, socketActiveTTL: 60000.
  • Nginx upstream: keepalive 64, keepalive_timeout 20s.
  • Клиентский keepalive_timeout в Nginx: 30–60s.

Внутренние микросервисы с низкой латентностью

Большие объемы RPS между сервисами в одном датацентре:

  • Node.js maxSockets: 300–800, maxFreeSockets: 0.3×maxSockets, freeSocketTimeout: 10000, socketActiveTTL: 30000.
  • Nginx upstream: keepalive 256, keepalive_requests 20000, keepalive_timeout 15s.

Слабомощные инстансы/узкие каналы

Ограничиваем конкаренси, чтобы не задушить CPU и сетевой стек:

  • Node.js maxSockets: 30–60, maxFreeSockets: 15–30, freeSocketTimeout: 20000.
  • Nginx upstream: keepalive 32, keepalive_timeout 20s.

Терминал с ss/ulimit и метриками upstream keepalive в Nginx

Диагностика и мониторинг

Смотрите на факты, а не на предположения. Минимальный набор наблюдений:

  • Состояния сокетов: ss -tanp | grep :3000, распределение по ESTABLISHED, TIME_WAIT, CLOSE_WAIT.
  • Системные лимиты: ulimit -n и фактическое использование файловых дескрипторов процессом.
  • Метрики Node.js: число pending-сокетов, длина очередей в агенте, ретраи/таймауты по типам ошибок.
  • Nginx: $upstream_connect_time, $upstream_response_time, доля 499/502/504, насыщение keepalive-пула.

Очень полезно логировать на стороне приложения id соединения или хеш сокета при отправке запросов — так вы увидите реальное переиспользование или его отсутствие.

Типичные ошибки

  • Не включили keepAlive в http.Agent и удивляетесь лавине TIME_WAIT.
  • freeSocketTimeout длиннее, чем серверный keepalive_timeout: периодические ECONNRESET при попытке переиспользования закрытого сервером соединения.
  • Поставили огромный maxSockets и «залили» upstream. Нужна координация с доступными воркерами и CPU на стороне сервиса.
  • В Nginx забыли proxy_http_version 1.1 и/или неверно выставили Connection, из-за чего пул upstream не работает.
  • Смешали настройки для клиентских соединений и для upstream, ожидая одинакового поведения — это разные плоскости.
  • Лечите TIME_WAIT через агрессивные sysctl. Это маскирует первопричину и добавляет нестабильность.

Серверная часть Node.js: не забудьте про keepAliveTimeout

Если ваш Node-сервис сам принимает HTTP, удостоверьтесь, что серверные таймауты согласованы:

const http = require('http');
const app = require('./app');

const server = http.createServer(app);
server.keepAliveTimeout = 65000;  // согласовать с Nginx keepalive_timeout 60s
server.headersTimeout = 67000;    // чуть больше keepAliveTimeout
server.requestTimeout = 30000;    // таймаут на обработку запроса

server.listen(3000);

Значение headersTimeout должно быть больше keepAliveTimeout, иначе Node может закрывать соединение, пока клиент еще законно шлет заголовки нового запроса.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

HTTP/2 и альтернатива Agent

HTTP/2 использует мультиплексирование в рамках одного TCP-соединения и отдельные настройки пула. В Node можно рассмотреть undici с H2 или встроенный модуль http2, но это отдельная тема: согласование H2 между Nginx и приложением, proxy_http_version останется 1.1 для H1 upstream, а для H2 требуется иная схема. Если вы остаетесь на HTTP/1.1 между Nginx и Node, описанные выше параметры остаются ключевыми.

Пошаговый чек-лист

  1. Включите keepAlive в http.Agent. Задайте значения maxSockets, maxFreeSockets, freeSocketTimeout, socketActiveTTL.
  2. Убедитесь, что ваша клиентская библиотека реально использует агент (axios: httpAgent/httpsAgent).
  3. В Nginx: proxy_http_version 1.1, proxy_set_header Connection "", включите upstream keepalive с разумными лимитами.
  4. Согласуйте таймауты: freeSocketTimeout немного меньше, чем соответствующий серверный keepalive_timeout.
  5. Проверьте метрики и логи: доля переиспользуемых соединений должна расти, TIME_WAIT — падать.
  6. Нагрузочное тестирование с реальными профилями. Регулируйте maxSockets и размеры пулов по факту.

Итог

Настройка keepalive — это координация клиентского пула (http.Agent) и прокси (Nginx). Делая их согласованными, вы снижаете накладные расходы на TCP/TLS, убираете лишние TIME_WAIT, стабилизируете латентность и предсказуемо контролируете backpressure через maxSockets. Это простые шаги, которые дают непропорционально большой выигрыш в реальной производительности.

Поделиться статьей

Вам будет интересно

Grafana Agent Flow на VDS: единый агент для metrics, logs и traces (Prometheus, Loki, Tempo, OTLP) OpenAI Статья написана AI (GPT 5)

Grafana Agent Flow на VDS: единый агент для metrics, logs и traces (Prometheus, Loki, Tempo, OTLP)

Grafana Agent в режиме Flow — лёгкий агент на одном VDS для метрик, логов и трейсов с отправкой в Prometheus/VictoriaMetrics, Loki ...
systemd-nspawn на VDS: лёгкие контейнеры, изоляция и сеть без Kubernetes OpenAI Статья написана AI (GPT 5)

systemd-nspawn на VDS: лёгкие контейнеры, изоляция и сеть без Kubernetes

Как запустить и подружить systemd-nspawn с вашим VDS: развертывание контейнеров, изоляция, bind mounts, сеть и cgroup-лимиты, упра ...
PostgreSQL: HOT UPDATE, fillfactor и борьба с bloat без боли OpenAI Статья написана AI (GPT 5)

PostgreSQL: HOT UPDATE, fillfactor и борьба с bloat без боли

HOT UPDATE экономит обновления индексов и сдерживает bloat, но срабатывает не всегда. Разбираем, как работает HOT, как выбрать fil ...