Предел одного nginx worker: connections, дескрипторы и где проходит реальная граница
Вы подняли worker_connections с 1024 до 10000, перезапустили nginx — и через день в error.log снова accept() failed (24: Too many open files), как будто ничего не менялось. Проблема в том, что директива worker_connections — это обещание самого nginx, а не гарантия операционной системы: сколько соединений вы реально сможете держать, решает ещё и лимит файловых дескрипторов, и память сервера. Ниже — как посчитать настоящий предел для конкретной машины, а не гадать по значению в конфиге.
Содержание
- Три независимых стены, а не одна цифра
- worker_connections и worker_processes: что реально считает nginx
- Файловые дескрипторы: одно соединение — не всегда один fd
- ulimit, LimitNOFILE и fs.file-max: три места, где реально стоит потолок ОС
- Память на соединение: буферы, TLS и keepalive
- Типичная ошибка и как посчитать реальный предел для своего сервера
Три независимых стены, а не одна цифра
Когда nginx принимает новое соединение, оно должно пройти сразу три проверки, и каждая — отдельный, не связанный с другими лимит:
- Конфигурация самого nginx —
worker_connectionsв блокеevents {}говорит: «я готов обслуживать не больше N соединений на один worker». Это верхняя граница со стороны приложения. - Лимит файловых дескрипторов ОС —
ulimit -nпроцесса и системныйfs.file-max. Каждое соединение — это как минимум один дескриптор, и ядро не выдаст их больше, чем разрешено, независимо от того, что написано в nginx.conf. - Память сервера — каждое открытое соединение резервирует буферы под заголовки, тело запроса и (для HTTPS) TLS-сессию. Дескрипторы почти бесплатны, а вот память — нет, и она кончается тише: без явной ошибки в логе, а через своп и деградацию.
Ключевой момент: эти три лимита не складываются и не усредняются. Сервер упрётся в тот, что самый узкий, а остальные два в этот момент будут иметь солидный запас. Именно поэтому поднять worker_connections в конфиге и не тронуть остальное — самая частая ошибка при тюнинге nginx под нагрузку: вы подвинули только одну из трёх стен.
worker_connections и worker_processes: что реально считает nginx
worker_connections задаёт максимум одновременных соединений на один worker-процесс, причём считаются не только клиентские сокеты, но и соединения к бэкенду, если nginx работает как reverse proxy или балансировщик.
events {
worker_connections 4096;
multi_accept on;
use epoll;
}
worker_processes auto;
Отсюда два практических следствия:
- Если у вас проксирование (upstream, балансировка, кэш с revalidation к origin), одно клиентское соединение может держать открытыми два сокета одновременно — к клиенту и к апстриму. Теоретический потолок по клиентам в этом случае ближе к
worker_connections / 2на воркер, а не к полному значению директивы. - Теоретический потолок сервера целиком —
worker_processes × worker_connections. Это верхняя граница «на бумаге», до которой вы почти никогда не дойдёте: раньше сработает лимит дескрипторов или памяти.
worker_processes auto берёт число ядер CPU — разумная точка отсчёта, но не догма: для сильно I/O-bound профиля (диск, сеть, ожидание апстрима) иногда выгоднее число воркеров чуть больше числа ядер, потому что часть времени они простаивают на ожидании I/O, а не считают на CPU. Дефолтное значение самого worker_connections различается между дистрибутивами и сборками (512, 768, 1024 — встречаются разные варианты), поэтому не полагайтесь на умолчание: задавайте явно и сверяйте действующий конфиг командой nginx -T | grep worker_connections.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверФайловые дескрипторы: одно соединение — не всегда один fd
Прежде чем считать лимиты ОС, важно понять, сколько дескрипторов реально тратит одно соединение — это не всегда единица:
- Простой HTTP-клиент без проксирования — 1 fd на клиентский сокет.
- Reverse proxy / балансировка — 2 fd: клиентский сокет плюс сокет к апстриму на время обработки запроса.
- Раздача статики с диска — на момент чтения файла добавляется ещё один транзитный fd на сам файл (
open()), который освобождается сразу после отдачи. - Логи, конфиги, сертификаты — держатся открытыми постоянно, но это фиксированное небольшое число дескрипторов на весь процесс, не растущее с числом соединений.
Из-за этого один и тот же сервер с одинаковым worker_connections может упереться в лимит дескрипторов на совершенно разном числе реальных клиентов — в зависимости от того, проксирует он или отдаёт статику напрямую. Подробный разбор того, сколько дескрипторов уходит на один запрос в разных сценариях, — в статье сколько дескрипторов тратит один запрос, а типичные симптомы их нехватки — в статье кончились file descriptors: симптомы.
Когда дескрипторы кончаются, ошибка в error.log выглядит однозначно:
accept() failed (24: Too many open files)
Стоит различать два похожих, но разных кода ошибки ядра: EMFILE — исчерпан лимит именно этого процесса (ulimit -n), а ENFILE — исчерпан общесистемный лимит на все процессы сразу (fs.file-max). Первое лечится правкой лимита юнита nginx, второе — правкой sysctl, и путать их не стоит: решение разного уровня.
ulimit, LimitNOFILE и fs.file-max: три места, где реально стоит потолок ОС
Лимит дескрипторов проверяется на трёх независимых уровнях, и nginx упрётся в самый строгий из них:
- Лимит процесса — управляется через
ulimit -nдля интерактивной сессии, но для nginx, запущенного через systemd, актуаленLimitNOFILEв unit-файле, а не/etc/security/limits.conf. Это частая ловушка:limits.confчитает только PAM при логине через getty/ssh, и на демон, стартующий из systemd, он не действует вообще. - Директива
worker_rlimit_nofileв самом nginx.conf — явно поднимает лимит для worker-процессов, но не выше того, что разрешает systemd/ядро. Если системный лимит выше, а nginx не попросил столько же явно, worker всё равно упрётся в собственный заниженный внутренний потолок. - Системный потолок —
fs.file-maxв sysctl, общий лимит на все процессы сервера сразу, а не только на nginx.
Проверка текущих значений:
# лимит процесса nginx (PID мастер-процесса)
cat /proc/$(cat /var/run/nginx.pid)/limits | grep "Max open files"
# системный потолок
sysctl fs.file-max
# сколько дескрипторов уже занято всеми процессами сервера
cat /proc/sys/fs/file-nr
# сколько дескрипторов реально держит nginx прямо сейчас
ls /proc/$(cat /var/run/nginx.pid)/fd | wc -l
Правка лимита для systemd-юнита:
sudo systemctl edit nginx
[Service]
LimitNOFILE=65535
После этого — systemctl daemon-reload && systemctl restart nginx, и обязательно синхронно поднять worker_rlimit_nofile 65535; в nginx.conf. Если значения разойдутся, победит меньшее из двух, и вы получите непонятный на первый взгляд отказ при вроде бы щедром systemd-лимите. Подробная настройка лимита открытых файлов и типичные грабли с limits.conf разобраны в статье настройка лимитов открытых файлов: ulimit.
Память на соединение: буферы, TLS и keepalive
Дескриптор — почти бесплатен для памяти, а буферы соединения — нет. На каждое активное соединение nginx резервирует память под несколько категорий буферов, заданных директивами:
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
client_body_buffer_size 16k;
# только при проксировании
proxy_buffer_size 4k;
proxy_buffers 8 4k;
При HTTPS добавляется память под саму TLS-сессию и, если включён ssl_session_cache, под общий кэш сессий — его размер задаётся явно (например, ssl_session_cache shared:SSL:10m), это фиксированный shared-кэш, не растущий бесконтрольно, но конечный.
Точное число мегабайт на одно соединение сильно зависит от вашей конфигурации — размера заголовков у реальных клиентов, наличия проксирования, включённого кэша — поэтому не берите чужие оценки «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), и даст вашу реальную оценку — только её и стоит подставлять в дальнейшие расчёты, а не цифру из чужой статьи. Отдельно стоит держать в уме, что keepalive-соединения продолжают занимать и fd, и память, даже когда клиент ничего не запрашивает — большой keepalive_timeout при высоком трафике незаметно съедает оба ресурса впрок. Если память nginx растёт со временем без роста трафика — это, возможно, отдельная проблема, не связанная с числом соединений напрямую; она разобрана в статье nginx: высокое потребление памяти.
Типичная ошибка и как посчитать реальный предел для своего сервера
Самая частая ошибка тюнинга ровно та, с которой начиналась статья: поднять worker_connections в nginx.conf, перезапустить сервис и считать вопрос закрытым. По умолчанию ulimit -n в системе часто остаётся на historically низком значении (в некоторых дистрибутивах и оболочках — 1024), и новое, щедрое значение worker_connections просто никогда не будет достигнуто: сервер упрётся в старый лимит дескрипторов на порядок раньше, а в error.log появится всё тот же accept() failed (24: Too many open files), как будто конфиг вообще не менялся.
Чтобы не гадать, посчитайте предел по шагам, а не подбирайте цифры вслепую:
- Определите целевое число одновременных соединений, исходя из ожидаемого трафика — это отправная точка, а не результат.
- Посчитайте бюджет дескрипторов. Для каждого соединения — 1 fd (простой HTTP) или 2 fd (проксирование). Целевое число соединений × fd на соединение не должно превышать
worker_rlimit_nofile/LimitNOFILEна процесс, а суммарно по всем worker-процессам — не превышатьfs.file-max. - Синхронизируйте
worker_rlimit_nofileиLimitNOFILEс запасом (обычно на 20–30% выше расчётного бюджета) — запас нужен на служебные дескрипторы (логи, сертификаты, сокеты мониторинга), которые тоже входят в общий лимит процесса. - Выставьте
worker_connectionsкак целевое число соединений, делённое наworker_processes, с тем же запасом — но не выше, чем позволяют шаги 2 и 5. - Посчитайте бюджет памяти. Доступная RAM после вычета резерва под ОС и другие сервисы, делённая на измеренную у себя память на соединение (см. предыдущий раздел), даёт memory-потолок по числу соединений.
- Возьмите минимум из трёх потолков — nginx-конфигурации, дескрипторов, памяти. Это и есть реальный предел сервера на сегодня, а не то число, что написано в
events {}.
Ниже — схема расчёта на условном примере, чтобы показать метод; цифры иллюстративные, подставьте свои измеренные значения:
| Шаг | Формула | Что ограничивает |
|---|---|---|
| nginx-потолок | worker_processes × worker_connections | конфигурация nginx |
| fd-потолок (без проксирования) | LimitNOFILE_на_процесс − служебные_fd | ulimit / systemd |
| fd-потолок (с проксированием) | (LimitNOFILE_на_процесс − служебные_fd) / 2 | ulimit / systemd |
| memory-потолок | (RAM_доступная) / (память_на_соединение_измеренная) | физическая память |
| реальный предел | min(nginx-потолок, fd-потолок, memory-потолок) | самый узкий из трёх |
Такой расчёт не заменяет проверку на реальном трафике — точный момент деградации по каждому ресурсу (CPU, задержки бэкенда, поведение под всплеском) лучше подтвердить контролируемым нагрузочным тестом, методика которого подробно разобрана в статье сколько соединений держит nginx: замер до отказа. Но расчёт по шагам выше даёт стартовую точку, от которой не стыдно отталкиваться, и сразу показывает, какой из трёх лимитов вы забыли подвинуть при последнем тюнинге.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Что будет, если поднять только worker_connections, ничего больше не трогая?
Ничего хорошего: nginx попытается принять больше соединений, чем разрешает ulimit -n процесса, и как только упрётся в реальный лимит дескрипторов, начнёт получать EMFILE от ядра — в логе появится accept() failed (24: Too many open files). Новое значение директивы останется красивой, но не работающей цифрой в конфиге.
Обязательно ли синхронизировать worker_rlimit_nofile с LimitNOFILE вручную?
Да. Если не указать worker_rlimit_nofile явно, worker-процессы наследуют лимит от родительского процесса и systemd-юнита, но полагаться на наследование рискованно — поведение может отличаться между версиями nginx и дистрибутивами. Безопаснее задать оба значения явно и держать их синхронными.
Нужно ли поднимать fs.file-max, если ulimit -n на процесс уже большой?
Обычно нет — fs.file-max в современных дистрибутивах по умолчанию достаточно велик для типичной нагрузки одного веб-сервера. Но если на машине крутится несколько демонов, каждый с собственным щедрым лимитом на процесс, суммарное потребление стоит сверить с fs.file-max через /proc/sys/fs/file-nr — общесистемный потолок один на всех.
Как понять, в какой из трёх лимитов упёрся сервер прямо сейчас, без нагрузочного теста?
По косвенным признакам: EMFILE/ENFILE в error.log — дескрипторы; OOM-killer в dmesg или рост в free -m/vmstat (столбцы si/so) — память; nginx работает штатно, но Active connections из stub_status держится заметно ниже расчётного nginx-потолка при высоком CPU — вы упёрлись в вычислительные ресурсы, а не в connections вовсе.
keepalive увеличивает предел соединений или уменьшает?
Ни то, ни другое напрямую — но он меняет то, как быстро вы упрётесь в fd- и memory-потолок под пиковой нагрузкой, потому что простаивающие keepalive-соединения продолжают занимать оба ресурса. При расчёте бюджета учитывайте не только активные, но и «висящие» keepalive-соединения как часть целевого числа.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →