Если ваши 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: практические рекомендации
Значения по умолчанию в Node.js часто не совпадают с вашими целями по пропускной способности и стабильности. Практический минимум, с которого стоит начинать:
keepAlive: true— обязательно. Без этого пула фактически нет.maxSockets: подбирается по нагрузке и возможностям upstream-а. Часто разумно ставить 50–200 на origin для API и до 500–1000 для внутренних шина-подобных сервисов с низкой латентностью. Если у вас несколько воркеров, умножайте на их число.maxFreeSockets: 1/4–1/2 отmaxSocketsобычно достаточно, чтобы держать «теплые» соединения и не тратить память зря.freeSocketTimeout(мс): подстраивайте под таймаут keepalive на стороне сервера/прокси (например, Nginxkeepalive_timeoutи upstreamkeepalive_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.
Согласование таймаутов 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 -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 может закрывать соединение, пока клиент еще законно шлет заголовки нового запроса.
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, описанные выше параметры остаются ключевыми.
Пошаговый чек-лист
- Включите
keepAliveвhttp.Agent. Задайте значенияmaxSockets,maxFreeSockets,freeSocketTimeout,socketActiveTTL. - Убедитесь, что ваша клиентская библиотека реально использует агент (axios:
httpAgent/httpsAgent). - В Nginx:
proxy_http_version 1.1,proxy_set_header Connection "", включитеupstream keepaliveс разумными лимитами. - Согласуйте таймауты:
freeSocketTimeoutнемного меньше, чем соответствующий серверныйkeepalive_timeout. - Проверьте метрики и логи: доля переиспользуемых соединений должна расти,
TIME_WAIT— падать. - Нагрузочное тестирование с реальными профилями. Регулируйте
maxSocketsи размеры пулов по факту.
Итог
Настройка keepalive — это координация клиентского пула (http.Agent) и прокси (Nginx). Делая их согласованными, вы снижаете накладные расходы на TCP/TLS, убираете лишние TIME_WAIT, стабилизируете латентность и предсказуемо контролируете backpressure через maxSockets. Это простые шаги, которые дают непропорционально большой выигрыш в реальной производительности.


