Почему nginx пишет Too many open files после увеличения worker_connections
Вы подняли `worker_connections` с 1024 до 8192, проверили конфигурацию, перезагрузили nginx — а в журнале снова `Too many open files`. Я регулярно встречаю эту картину. Причина обычно не в «неправильном nginx», а в смешении двух разных ограничителей: числа соединений, которыми готов управлять worker, и числа файловых дескрипторов, которое Linux разрешил открыть процессу. Ниже покажу, как я различаю эти лимиты, считаю клиентские и upstream-соединения и исправляю проблему без бессмысленного выкручивания всех значений в миллион.
worker_connections не поднимает лимит файлов
worker_connections задаёт максимальное число одновременных соединений для одного worker-процесса nginx. Именно для одного, а не для всего сервера. При четырёх workers и значении 8192 получается теоретически до 32 768 соединений, но это не общий равномерно распределяемый пул. Если один worker упёрся в предел, свободное место у соседнего его не спасёт. Кроме того, фактическое число соединений не может превысить доступное процессу число файловых дескрипторов — это прямо оговорено в документации nginx.
Файловый дескриптор — более широкое понятие. Он занят клиентским TCP-сокетом, соединением к upstream, listen-сокетом, открытым логом, статическим или временным файлом, eventfd, каналом между master и worker. Если включён open_file_cache, nginx по документации кеширует дескрипторы открытых файлов вместе с размером и временем модификации, и при max=10000 это до десяти тысяч дескрипторов на каждый worker сверх сетевых. Поэтому равенство «8192 connections означает достаточно 8192 файлов» я не использую. Для обычного reverse proxy один активный запрос часто удерживает минимум два сокета в процессе nginx: клиентский и серверный. Это не два дескриптора у одного сокета, а два разных сокета.
Рабочая модель у меня такая: worker_connections ограничивает соединения nginx, а RLIMIT_NOFILE — все дескрипторы процесса. Для прокси приблизительный расчёт на worker выглядит так: клиентские сокеты плюс активные и idle upstream-сокеты плюс открытые файлы и служебный запас. Эффективный потолок оказывается меньшим из двух значений: worker_connections и RLIMIT_NOFILE за вычетом несетевых дескрипторов. При HTTP/2 арифметика ещё менее линейна: один клиентский TCP-сеанс несёт много потоков, которые могут породить несколько соединений к backend.
- `worker_connections` действует отдельно для каждого worker.
- Соединения с proxied-серверами тоже входят в `worker_connections`.
- `RLIMIT_NOFILE` учитывает сокеты, файлы, pipes и служебные дескрипторы.
- Показатель Active из `stub_status` отражает клиентские соединения, но не даёт полной картины upstream и файлов.
Как я за пять минут нахожу настоящий предел
Сначала смотрю точную ошибку. accept4() failed (24: Too many open files) означает, что worker не смог принять клиента. socket() failed (24: Too many open files) while connecting to upstream — не смог создать сокет к приложению. open() failed (24: Too many open files) — закончились дескрипторы при открытии файла. Код 24 — это EMFILE, исчерпан лимит конкретного процесса. Системное исчерпание таблицы файлов даёт ENFILE и обычно формулировку Too many open files in system. Разница принципиальная.
Затем проверяю не свой интерактивный ulimit -n, а работающие процессы nginx. Лимиты наследуются по цепочке запуска, поэтому значение в SSH-сессии ничего не доказывает для сервиса, созданного PID 1. Команды выполняю от root или через sudo:
master_pid=$(cat /run/nginx.pid)
grep 'Max open files' /proc/$master_pid/limits
for worker_pid in $(pgrep -P "$master_pid"); do
printf 'worker=%s fd=' "$worker_pid"
ls -1 /proc/$worker_pid/fd | wc -l
grep 'Max open files' /proc/$worker_pid/limits
done
systemctl show nginx -p LimitNOFILE -p LimitNOFILESoft
nginx -T 2>&1 | grep -E 'worker_(processes|connections|rlimit_nofile)'Если один worker близок к soft limit, исследую именно его: ls -l /proc/PID/fd показывает ссылки на сокеты, файлы, pipes и anonymous inode. Для быстрой сетевой сверки добавляю ss -ntp, но не пытаюсь вывести точный FD-бюджет только из ss. Отдельно смотрю cat /proc/sys/fs/file-nr и cat /proc/sys/fs/file-max. Если системная таблица далека от заполнения, fs.file-max оставляю в покое: поднятие глобального лимита не лечит EMFILE у worker.
- Найти PID worker, указанный перед двоеточием в сообщении nginx.
- Сравнить его текущее число записей в `/proc/PID/fd` с soft limit.
- Проверить effective-конфигурацию через `nginx -T`, включая подключаемые файлы.
- Посмотреть unit и drop-in-файлы командой `systemctl cat nginx`.
- Только после измерений рассчитывать новый предел.
Практический разбор: текстильный магазин «Текстильная лавка»
Название «Текстильная лавка» условное. Это текстильный магазин на 32 рабочих места: розничный зал, склад и интернет-витрина с каталогом тканей и домашнего текстиля. Нагрузку на nginx создают покупатели, картинки каталога и выгрузки для маркетплейсов, поэтому штат здесь почти ни о чём не говорит. На входе стояли две одинаковые виртуальные машины: по 4 vCPU и 8 Гбайт RAM, Ubuntu Server 24.04 LTS, ядро 6.8, nginx 1.24.0 из репозитория Ubuntu и systemd 255. Nginx завершал TLS, отдавал изображения и проксировал запросы в три HTTP-upstream. На каждой машине работали четыре worker-процесса.
Перед рекламной кампанией администратор увидел старое worker_connections 1024 и увеличил его до 8192. Конфигурация стала такой:
worker_processes auto;
events {
worker_connections 8192;
}Проверка nginx -t проходила. В его SSH-сессии ulimit -n показывал 65535. Казалось, запас огромный. Но во время сезонной распродажи, при примерно 1350 запросах в секунду на двух узлах вместе с картинками каталога и опросом остатков маркетплейсами, один frontend начал отдавать 502 и рвать новые TLS-соединения. В error log одновременно появились ошибки accept4() и socket() с кодом 24.
На проблемном worker я насчитал 4089 открытых дескрипторов. В /proc/PID/limits стояло Max open files 4096 4096. Приблизительная раскладка была такой: 1706 клиентских сокетов, 1932 активных соединения к backend, 384 idle keepalive-соединения из трёх upstream-групп и 67 listen-сокетов, логов, локальных файлов и служебных объектов. Сумма — 4089. До лимита оставалось семь дескрипторов. Остальные workers держали от 2400 до 3300, поэтому среднее по серверу выглядело терпимо.
Почему upstream оказалось так много? Во время кампании складская интеграция замедлила ответы API, p95 вырос почти до 2,7 секунды. Клиенты продолжали приходить, старые запросы ещё ждали backend, а кеши upstream keepalive сохраняли по 128 idle-соединений на группу и worker. worker_connections 8192 не был достигнут: соединений было около четырёх тысяч. Первым закончился RLIMIT_NOFILE. Значение 65535 из SSH не имело отношения к сервису: в каталоге /etc/systemd/system/nginx.service.d/ лежал забытый drop-in прежнего подрядчика с LimitNOFILE=4096, а worker_rlimit_nofile в nginx.conf не было, поэтому workers унаследовали 4096 от master. Без этого drop-in сервис получил бы системный дефолт DefaultLimitNOFILE=1024:524288, и soft limit 1024 закончился бы ещё раньше.
- Пиковая нагрузка: около 1350 HTTP-запросов в секунду на два frontend-узла.
- Лимит проблемного worker: 4096 soft и 4096 hard.
- Фактическое число FD перед отказом: 4089.
- Настроенный предел nginx: 8192 соединения на worker.
- Триггер: рост времени ответа upstream, а не скачок загрузки CPU или нехватка RAM.
Какой конфиг я поставил и почему
Я не вычисляю лимит впритык. Беру измеренный пик, моделирую более медленный backend и оставляю минимум 50 % запаса. В «Текстильной лавке» мы ожидали до 6000–7000 одновременно учитываемых соединений на самый загруженный worker. Я поставил worker_connections 16384, а файловый лимит — 65536. Четырёхкратная разница намеренная: есть пространство для файлов, временных объектов, upstream-пулов и неравномерной нагрузки. Миллион здесь ничего полезного не добавлял.
worker_processes auto;
worker_rlimit_nofile 65536;
events {
worker_connections 16384;
}Одновременно зафиксировал лимит в systemd через drop-in, а не правил пакетный unit в /lib/systemd/system: пакетный файл перезапишет следующее обновление. Забытый drop-in с 4096 удалил, а новый создал командой sudo systemctl edit nginx, которая открывает /etc/systemd/system/nginx.service.d/override.conf:
[Service]
LimitNOFILE=65536По man systemd.exec одна величина задаёт одинаковые soft и hard limit, а форма LimitNOFILE=65536:524288 задаёт их раздельно. Hard limit не может превысить fs.nr_open ядра. worker_rlimit_nofile при таком unit формально дублирует уже достаточный лимит, но я оставляю обе настройки осознанно: unit определяет среду запуска, nginx-конфигурация явно документирует предел workers. Это проще проверять следующему администратору.
Здесь важная тонкость. Документация nginx прямо говорит, что worker_rlimit_nofile нужен, чтобы поднять лимит workers без перезапуска master: master под root вызывает setrlimit() для новых workers при reload. Это экстренный вариант. А вот новый LimitNOFILE из drop-in systemd применяет только при запуске процесса, одного nginx -s reload для этого недостаточно. На двух узлах мы делали рестарт по очереди, предварительно выводя узел из балансировки:
sudo nginx -t
sudo systemctl daemon-reload
sudo systemctl restart nginx
systemctl show nginx -p LimitNOFILE -p LimitNOFILESoft
master_pid=$(cat /run/nginx.pid)
for worker_pid in $(pgrep -P "$master_pid"); do
grep 'Max open files' /proc/$worker_pid/limits
doneЕсли nginx работает в контейнере, меняйте лимит ещё и на уровне контейнерного runtime или оркестратора. Drop-in хостового nginx.service внутренний процесс контейнера может вообще не затрагивать.
- Сначала определить реальный пик FD самого загруженного worker.
- Заложить запас на задержки backend и открытые файлы.
- Согласовать `worker_connections`, `worker_rlimit_nofile` и `LimitNOFILE`.
- Проверить `/proc/PID/limits` уже у новых worker-процессов.
- Выполнять restart поочерёдно, если сервис нельзя прерывать.
Что обычно делают зря или опасно
Первая ошибка — редактировать /etc/security/limits.conf и перезагружать nginx. Этот файл читает модуль pam_limits, и по man limits.conf лимиты задаются на вход в систему (per login). Системный nginx.service запускает PID 1 без PAM-сессии, поэтому limits.conf на него не действует. Сервис получает лимиты из директив unit, а если их нет — из DefaultLimitNOFILE= в systemd-system.conf, по умолчанию 1024:524288. Вторая — проверять ulimit -n под root или пользователем www-data. Эта команда показывает лимит текущей оболочки и её потомков. Я смотрю systemctl show и /proc/PID/limits; остальное годится только как дополнительная подсказка.
Третья ошибка — сразу поднимать fs.file-max, fs.nr_open, worker_connections и LimitNOFILE до миллиона. Большое число само по себе не ускоряет nginx. Оно лишь убирает один предохранитель и позволяет накопить больше соединений, памяти и нагрузки на backend. По документации ядра fs.file-max — максимум file handles, которые ядро выделит всей системе, а fs.nr_open — максимум дескрипторов на один процесс, по умолчанию 1024*1024 = 1048576. Выше fs.nr_open нельзя поднять ни LimitNOFILE, ни worker_rlimit_nofile. Я меняю их только после доказательства, что упёрлись именно туда. В большинстве небольших и средних установок проблема находится на уровне процесса или unit.
Четвёртая ошибка — лечить симптомы агрессивными таймаутами. Уменьшить keepalive_timeout или proxy_read_timeout иногда полезно, если соединения действительно висят дольше бизнес-смысла. Но обрывать оформление заказа через пять секунд, потому что склад отвечает семь, — плохая эксплуатация. Сначала я исправляю лимит, затем отдельно разбираю задержку backend, размеры keepalive-пулов и защиту приложения от перегрузки. Уменьшать upstream keepalive до нуля тоже не люблю: получите больше TCP-рукопожатий и новую проблему вместо старой.
- Не ориентироваться на `ulimit -n` в случайной shell-сессии.
- Не считать `stub_status` полным счётчиком дескрипторов.
- Не поднимать глобальные sysctl без признаков `ENFILE`.
- Не использовать короткие таймауты как замену нормальной ёмкости.
- Не умножать workers только ради большего суммарного числа connections.
Как проверяю результат и оставляю мониторинг
После изменения я повторяю не только синтаксическую проверку, но и профиль нагрузки с реальными keepalive, TLS, размерами ответов и задержкой upstream. Слежу за максимальным FD среди workers, а не за суммой или средним. Полезная простая проверка выглядит так:
master_pid=$(cat /run/nginx.pid)
for worker_pid in $(pgrep -P "$master_pid"); do
fd_count=$(ls -1 /proc/$worker_pid/fd | wc -l)
printf '%s %s\n' "$worker_pid" "$fd_count"
done
journalctl -u nginx --since '-15 min' \
| grep -E 'Too many open files|worker_connections are not enough'В постоянном мониторинге я вывожу FD count и лимит по каждому PID, а предупреждение ставлю примерно на 70–80 % с учётом нормального профиля.
В «Текстильной лавке» провели 45-минутный тест на 2600 запросах в секунду суммарно и искусственно задерживали часть ответов API до трёх секунд. Наиболее загруженный worker дошёл до 10 812 дескрипторов при лимите 65 536 и остался ниже worker_connections 16384 по соединениям. Ошибок accept4() и socket() с EMFILE не было. Затем команда приложения устранила задержку складской интеграции, и рабочий пик опустился примерно до 5200 FD. В итоге запас получился большим, но объяснимым — я предпочитаю такой запас случайному числу без измерений.
Мой порядок приоритетов простой. Сначала точный PID и фактический RLIMIT_NOFILE. Потом состав дескрипторов и число upstream-соединений. Затем согласованные настройки nginx и systemd, контролируемый restart и нагрузочный тест. На fs.file-max, ручной выбор epoll и микротюнинг таймеров можно пока забить, если измерения не показывают проблему именно там. Такой порядок обычно возвращает сервис быстрее и не маскирует медленный backend.
- Контролировать FD отдельно по каждому worker PID.
- Сопоставлять FD с soft limit, а не только с `worker_connections`.
- Проверять ошибки `EMFILE` и предупреждения nginx после релиза.
- Тестировать сценарий замедления backend, а не только быстрые ответы.
- Пересматривать лимиты после добавления upstream, WebSocket, HTTP/2 или файлового кеша.
Частые вопросы
Достаточно ли добавить только worker_rlimit_nofile?
Часто да: master под root поднимает `RLIMIT_NOFILE` workers до указанного значения, если оно не выше `fs.nr_open` и у процесса есть CAP_SYS_RESOURCE (в контейнерах её нередко нет). Я всё равно явно задаю `LimitNOFILE` в systemd и проверяю результат в `/proc/PID/limits`: так конфигурация предсказуема после обновлений и перезапусков.
Сколько клиентских подключений выдержит worker_connections 8192?
Универсального ответа нет. Для reverse proxy активный клиент обычно требует ещё соединение к upstream, а часть бюджета занимают idle keepalive. При грубой схеме один клиент плюс один upstream потолок будет заметно ниже 8192 клиентов.
Почему лимит заканчивается только у одного worker?
Ограничение действует на процесс, а распределение не обязано быть идеально ровным. Долгие keepalive, WebSocket, HTTP/2 и разная продолжительность запросов постепенно создают перекос.
Нужно ли менять fs.file-max?
Только если системная таблица файлов действительно близка к пределу или появляется `ENFILE`. При обычном `EMFILE` у nginx изменение `fs.file-max` не решает проблему.
Можно ли применить настройку без restart?
Частично. `worker_rlimit_nofile` применяется через reload: master выставит новый лимит новым workers, так документация nginx и описывает назначение директивы. Новый `LimitNOFILE` из systemd получает только заново запущенный master. После изменения drop-in нужен restart сервиса; на отказоустойчивом контуре его делают по узлам.
Источники
- nginx: Основная функциональность — Директивы worker_connections (в число входят соединения с проксируемыми серверами) и worker_rlimit_nofile. https://nginx.org/ru/docs/ngx_core_module.html#worker_connections
- nginx: Модуль ngx_http_core_module — Директива open_file_cache: кеширование дескрипторов открытых файлов. https://nginx.org/ru/docs/http/ngx_http_core_module.html#open_file_cache
- NGINX: Module ngx_http_upstream_module — Раздел keepalive, учёт idle-соединений и примечания об изменениях начиная с nginx 1.29.7. https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive
- NGINX: stub_status module — Определение Active, Reading, Writing и Waiting для клиентских соединений. https://nginx.org/en/docs/http/ngx_http_stub_status_module.html
- systemd.exec(5) — Раздел Process Properties: LimitNOFILE=, формат soft:hard, hard limit по умолчанию 524288. https://man7.org/linux/man-pages/man5/systemd.exec.5.html
- systemd-system.conf(5) — DefaultLimitNOFILE= по умолчанию 1024:524288 для unit без явного лимита. https://man7.org/linux/man-pages/man5/systemd-system.conf.5.html
- Linux kernel documentation: /proc/sys/fs — Параметры file-max, file-nr и nr_open (по умолчанию 1048576). https://docs.kernel.org/admin-guide/sysctl/fs.html
- Linux man-pages: limits.conf(5) — Настройки pam_limits, применяемые на вход в систему (per login). https://man7.org/linux/man-pages/man5/limits.conf.5.html
- Linux man-pages: getrlimit(2) — RLIMIT_NOFILE, ошибка EMFILE, наследование и проверка лимитов процесса; Linux man-pages 6.18. https://man7.org/linux/man-pages/man2/getrlimit.2.html
- Linux man-pages: proc_pid_limits(5) — Формат `/proc/PID/limits`, soft и hard limits; Linux man-pages 6.18. https://man7.org/linux/man-pages/man5/proc_pid_limits.5.html
- Linux man-pages: proc_pid_fd(5) — Содержимое `/proc/PID/fd` и типы файловых дескрипторов; Linux man-pages 6.18. https://man7.org/linux/man-pages/man5/proc_pid_fd.5.html
- Linux man-pages: proc_sys_fs(5) — Системные параметры file-max и file-nr, отличие ENFILE от процессного RLIMIT_NOFILE. https://man7.org/linux/man-pages/man5/proc_sys_fs.5.html
- NGINX: Controlling nginx — Поведение reload: запуск новых workers и корректное завершение старых. https://nginx.org/en/docs/control.html
