MAATRIX / Блог / Сколько одновременных соединений держит ваш nginx: замер до отказа

Сколько одновременных соединений держит ваш nginx: замер до отказа

MAATRIX

Когда в статьях пишут «nginx держит десятки тысяч соединений на одном ядре», это правда — и одновременно бесполезная цифра для вашего конкретного сервера. Реальный потолок складывается из трёх независимых лимитов: конфигурации самого nginx, ограничений операционной системы и объёма памяти, которую съедает каждое соединение. Один из них обязательно упрётся первым, и узнать, какой именно — можно только замером на своём железе, а не поиском «default nginx max connections» в гугле.

Из чего складывается потолок: три независимые стены

Соединение до nginx не «просто открывается» — оно проходит через несколько слоёв, и на каждом стоит свой лимит:

  • Конфигурация nginx — сколько параллельных соединений готов принять каждый worker-процесс и что он будет с ними делать (события, backlog, keepalive).
  • Лимиты ядра и systemd — сколько файловых дескрипторов вообще может открыть процесс nginx, независимо от того, что написано в его конфиге.
  • Память сервера — каждое соединение резервирует буферы на чтение заголовков, тело запроса, ответ, а для HTTPS — ещё и сессионный кэш TLS. Кончится память раньше, чем кончатся дескрипторы — вы получите OOM или своп, а не аккуратный отказ на уровне nginx.

Важно понимать: эти три лимита не суммируются и не усредняются — сервер откажет по самому узкому месту. Если worker_connections стоит 1024, а файловых дескрипторов разрешено миллион — вы упрётесь в 1024 задолго до системного лимита. И наоборот: щедрый worker_connections с зажатым ulimit -n оборвётся на системном лимите, а nginx в логах честно напишет об этом.

worker_connections, worker_processes и events {} — что они реально ограничивают

Директива worker_connections в блоке events задаёт максимальное число одновременных соединений на один worker-процесс, включая соединения к клиентам и, если nginx работает как reverse proxy, соединения к бэкенду. Это ключевой нюанс: если у вас проксирование, одно клиентское соединение может держать открытыми два файловых дескриптора одновременно — к клиенту и к апстриму. Формула теоретического максимума клиентов при проксировании — примерно worker_connections / 2 на каждый worker, а не полное значение директивы.

events {
    worker_connections  4096;
    multi_accept        on;
    use                 epoll;
}

worker_processes  auto;

Несколько практических моментов:

  • worker_processes auto берёт число ядер CPU — это разумный старт, но не панацея: если сервер сильно I/O-bound (диск, сеть), иногда выгоднее чуть больше воркеров, чем ядер.
  • Значение worker_connections по умолчанию отличается между дистрибутивами и версиями nginx (в разных сборках можно встретить и 512, и 768, и 1024) — не полагайтесь на дефолт, задавайте явно и проверяйте текущее значение командой nginx -T | grep worker_connections.
  • multi_accept и выбор метода событий (epoll на Linux) влияют на то, как эффективно worker забирает новые соединения из очереди при резком всплеске, но не увеличивают сам лимит.
  • Теоретический потолок сервера — worker_processes × worker_connections, но это верхняя граница, до которой вы почти никогда не дойдёте: раньше упрётесь в дескрипторы или память.

Отдельно стоит listen ... backlog=511 (значение по умолчанию тоже меняется в зависимости от версии и ОС) — это очередь уже принятых на уровне ядра, но ещё не обработанных nginx соединений. Если соединения приходят быстрее, чем worker успевает их забрать, а backlog переполнен, клиент получит connection refused ещё до того, как nginx вообще узнает о попытке подключения. Проверить текущий backlog и его переполнение можно через ss -ltn, столбец Recv-Q/Send-Q покажет очередь на слушающем сокете.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Файловые дескрипторы: где ОС остановит вас раньше nginx

Каждое TCP-соединение, каждый открытый файл (включая логи, конфиги, upstream-сокеты) — это один файловый дескриптор. Linux ограничивает их число на нескольких уровнях одновременно, и nginx упрётся в самый жёсткий из них:

  1. Лимит процессаulimit -n, для systemd-юнитов задаётся через LimitNOFILE в unit-файле, а не в /etc/security/limits.conf (этот файл читает только PAM при логине через getty/ssh и на демон systemd не действует).
  2. Директива worker_rlimit_nofile в самом nginx.conf — явно поднимает лимит для worker-процессов, но не выше того, что разрешает systemd/ядро.
  3. Системный потолокfs.file-max в sysctl, это лимит на все процессы сервера суммарно.

Проверка текущих значений:

# лимит процесса nginx (замените PID на pid мастер-процесса)
cat /proc/$(cat /var/run/nginx.pid)/limits | grep "Max open files"

# системный потолок
sysctl fs.file-max

# сколько дескрипторов реально открыто прямо сейчас
ls /proc/$(cat /var/run/nginx.pid)/fd | wc -l

Если nginx запущен через systemd, лимит правится в unit-файле или в override:

sudo systemctl edit nginx
[Service]
LimitNOFILE=65535

После этого — systemctl daemon-reload && systemctl restart nginx и обязательно worker_rlimit_nofile 65535; в nginx.conf, чтобы значения совпадали: если системный лимит выше, а nginx не попросил столько же явно, он всё равно упрётся в свой заниженный внутренний потолок. Подробно про настройку системного лимита открытых файлов и грабли с limits.conf разобраны в статье про ulimit и лимиты открытых файлов.

Когда дескрипторы кончаются, в error.log nginx появляется характерная запись accept() failed (24: Too many open files) — это однозначный маркер того, что вы упёрлись именно в этот лимит, а не в CPU или память.

Память: сколько стоит одно соединение и куда она уходит

Файловый дескриптор — это почти бесплатно с точки зрения памяти, а вот буферы под соединение — нет. На каждое активное соединение nginx резервирует память под несколько категорий буферов, заданных директивами вроде client_header_buffer_size, large_client_header_buffers, client_body_buffer_size, а при проксировании — ещё proxy_buffers и proxy_buffer_size на каждое соединение с апстримом. Для HTTPS добавляется память под TLS-сессию и, если включён ssl_session_cache, под общий кэш сессий (его размер вы задаёте явно, например ssl_session_cache shared:SSL:10m — это фиксированный shared-кэш, не растущий бесконтрольно, но конечный).

Точное число мегабайт на одно соединение сильно зависит от вашей конкретной конфигурации (buffers, keepalive, наличие проксирования, размер заголовков у ваших клиентов) — не берите чужие оценки «N килобайт на коннект» как истину, замерьте на своём конфиге. Практический способ:

# память nginx до нагрузки
ps -o pid,rss,vsz,cmd -C nginx

# память под нагрузкой — сравните RSS worker-процессов
watch -n 2 'ps -o pid,rss,cmd -C nginx | grep worker'

Разница в RSS между «до» и «под пиковой нагрузкой», делённая на число активных соединений на тот момент (смотрите через stub_status, см. ниже), и даст вашу реальную оценку памяти на соединение. Отдельная частая проблема — рост потребления памяти nginx со временем без роста трафика (утечка в модуле, разрастание кэша, keepalive-соединения, которые не закрываются) — это разбирается отдельно в статье про высокое потребление памяти nginx, полезно исключить эту причину перед тем, как списывать нехватку памяти на «просто много соединений».

Не забывайте про буфер ядра на сами TCP-сокеты (net.ipv4.tcp_rmem, net.ipv4.tcp_wmem) — это память вне контроля nginx, но она тоже растёт линейно с числом соединений и может обрушить сервер в своп раньше, чем сработает какой-либо лимит самого nginx.

Методика нагрузочного теста «до отказа»

Вместо того чтобы искать «типичные цифры», проведите контролируемый тест на собственном сервере — это единственный способ узнать реальный потолок именно вашей конфигурации, железа и трафика.

Важно: никогда не гоняйте такой тест на проде. Поднимите отдельный тестовый инстанс с идентичным конфигом nginx или выделите окно с предупреждением пользователей. Про то, как нагрузочный тест может случайно положить продакшн, если границы не разделены явно, есть отдельный разбор: нагрузочный скрипт три недели бил по продакшену — хороший пример, почему тестовый и боевой контуры должны быть физически разными.

Порядок действий:

  1. Включите stub_status — встроенный модуль nginx, отдающий текущее число активных соединений в реальном времени:
location /nginx_status {
    stub_status;
    allow 127.0.0.1;
    deny all;
}
  1. Настройте мониторинг ресурсов заранее, а не постфактум:
# память и своп раз в секунду
vmstat 1

# fd worker-процессов
watch -n 1 'ls /proc/$(pgrep -f "nginx: worker" | head -1)/fd | wc -l'

# CPU по ядрам
mpstat -P ALL 1

# соединения на уровне ОС
ss -s
  1. Генерируйте нагрузку с постепенным наращиванием параллелизма, а не сразу «в полную мощность». Инструменты вроде wrk или hey умеют держать заданное число одновременных keep-alive соединений:
# пример: наращивание с шагом, каждый прогон — 60 секунд
wrk -t8 -c500  -d60s --latency https://ваш-тестовый-домен/
wrk -t8 -c2000 -d60s --latency https://ваш-тестовый-домен/
wrk -t8 -c5000 -d60s --latency https://ваш-тестовый-домен/

Флаг -c задаёт число одновременных соединений, -t — потоки самого wrk (не путайте с worker'ами nginx). Наращивайте -c шагами (например, ×2 на каждом прогоне) и на каждом шаге фиксируйте: значение Active connections из stub_status, RSS worker-процессов, число открытых fd, load average и, главное — есть ли ошибки и рост латентности в выдаче wrk --latency (колонки p99/p999).

  1. Зафиксируйте момент деградации — не обязательно полного падения. Резкий рост p99-латентности, появление таймаутов или ошибок 502/504/499 в access.log — это уже сигнал, что сервер работает на пределе, даже если формально ещё отвечает. Полный отказ (connection refused, зависание) — это уже следующая, более жёсткая стадия.

Как понять, какой ресурс кончился первым

Когда сервер начал деградировать, определите виновника по конкретным симптомам — гадать не нужно, у каждого лимита свой характерный след:

СимптомЧто смотретьВероятная причина
accept() failed (24: Too many open files) в error.logulimit -n процесса, worker_rlimit_nofileУпёрлись в лимит файловых дескрипторов
OOM-killer в dmesg/journalctl -k, резкий рост в free -mRSS worker'ов, vmstat (столбец so/si)Кончилась память, начался своп или OOM
Load average сильно выше числа ядер, CPU 100% на всех ядрахmpstat -P ALL, top по worker-процессамУпор в CPU — не хватает вычислительных ресурсов, а не соединений
Recv-Q растёт на слушающем сокете, но fd и память в нормеss -ltn, net.core.somaxconn vs listen backlogПереполнен backlog очереди приёма — nginx не успевает забирать соединения из ядра
Ошибки 502/504 у бэкенда при нормальном nginxproxy_read_timeout, метрики апстримаУпор не в сам nginx, а в приложение за ним

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

Что делать с результатом теста

Полученный потолок нужен не как красивая цифра для отчёта, а как основа для трёх решений: где выставить worker_connections с запасом (обычно чуть ниже точки деградации, а не впритык к ней), нужен ли апгрейд по CPU/RAM или узкое место сугубо в конфигурации, и на каком уровне ставить алерт мониторинга (например, по Active connections из stub_status или по темпу роста fd), чтобы узнать о приближении к пределу заранее, а не постфактум по жалобам пользователей. Если тест стабильно показывает упор в память или CPU при разумных значениях worker_connections — это уже вопрос не тюнинга конфига, а вертикального масштабирования сервера. Общий подход к расчёту конфигурации сервера под ожидаемую нагрузку, включая ту же логику «считать, а не гадать», разобран в статье как рассчитать конфигурацию сервера под нагрузку.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

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

Можно ли просто поставить worker_connections побольше и не заморачиваться с тестом?

Можно, но это не решает проблему — если системный лимит дескрипторов или память сервера меньше, чем требует новое значение, nginx либо не сможет открыть столько соединений, либо сервер уйдёт в своп при попытке. Большое значение директивы без соответствующих системных лимитов и памяти — это просто цифра в конфиге, не отражающая реальность.

Нужно ли тестировать на HTTP и HTTPS отдельно?

Да, обязательно. TLS-хендшейк и сессионный кэш добавляют заметную нагрузку на CPU и память по сравнению с обычным HTTP, и потолок по HTTPS почти всегда ниже — иногда существенно, в зависимости от cipher suite и того, включён ли session resumption.

Как часто нужно повторять такой замер?

Разумно — после значимых изменений: смены версии nginx, апгрейда/даунгрейда железа, изменения конфигурации (добавили проксирование, TLS-терминацию, новый location с большими буферами) или заметного изменения профиля трафика. Регулярный ежемесячный прогон на тестовом стенде тоже не будет лишним для сервисов с растущей нагрузкой.

Что если в тесте деградация наступает намного раньше, чем ожидалось?

Сначала исключите сам инструмент нагрузки — убедитесь, что генератор (wrk/hey) не упирается в собственные лимиты по файловым дескрипторам или сети раньше тестируемого сервера. Запустите тест с нескольких машин параллельно, если один генератор сам стал узким местом.

worker_rlimit_nofile обязательно указывать явно?

Если не указать, worker-процессы наследуют лимит от родительского процесса и systemd-юнита. На практике безопаснее задать явно и держать в конфиге синхронно с LimitNOFILE, чтобы не зависеть от значений по умолчанию, которые могут отличаться между дистрибутивами.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →