АйТи Фреш
Главная / Статьи / Серверы и хостинг
Серверы и хостинг

Почему nginx пишет Too many open files после увеличения worker_connections

Автор: Семёнов Евгений Сергеевич, директор ООО «АйТи-Фреш» · · ~15 мин чтения
Почему nginx пишет Too many open files после увеличения worker_connections
Иллюстрация к статье «Почему 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` не изменяет `RLIMIT_NOFILE`. Если worker получил лимит 4096 файлов, значение `worker_connections 8192` не позволит ему перепрыгнуть через эти 4096.
Памятка: worker_connections не поднимает лимит файлов — схема
Памятка: worker_connections не поднимает лимит файлов. Открыть схему в полном размере

Как я за пять минут нахожу настоящий предел

Сначала смотрю точную ошибку. 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.

Снимайте показатели во время пика или воспроизводите нагрузку. Утром worker может держать 150 дескрипторов, а во время замедления backend — четыре тысячи. Спокойный сервер не показывает причину аварии.
Почему nginx пишет Too many open files после увеличения worker_connections — схема
Схема к статье. Открыть схему в полном размере

Практический разбор: текстильный магазин «Текстильная лавка»

Название «Текстильная лавка» условное. Это текстильный магазин на 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 закончился бы ещё раньше.

Проблема проявилась не ровно на 4096 клиентских подключениях. Клиентов было меньше половины FD-бюджета. Остальное съели upstream и служебные дескрипторы — именно эту часть чаще всего забывают.

Какой конфиг я поставил и почему

Я не вычисляю лимит впритык. Беру измеренный пик, моделирую более медленный 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 внутренний процесс контейнера может вообще не затрагивать.

Не ставьте `worker_rlimit_nofile` выше того, что реально допускает среда запуска: выше `fs.nr_open` или в контейнере без CAP_SYS_RESOURCE вызов `setrlimit()` не пройдёт, и в error log появится `setrlimit(RLIMIT_NOFILE, ...) failed`. И не считайте успешный `nginx -t` доказательством применённого runtime-лимита. Истина находится в `/proc/PID/limits` работающего worker.
Порядок действий: Какой конфиг я поставил и почему — схема
Порядок действий: Какой конфиг я поставил и почему. Открыть схему в полном размере

Что обычно делают зря или опасно

Первая ошибка — редактировать /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-рукопожатий и новую проблему вместо старой.

Высокий лимит файлов не является уязвимостью сам по себе, но он расширяет возможный расход ресурсов. Сохраняйте ограничения соединений, rate limiting и контроль ёмкости upstream — особенно на публичном контуре.

Как проверяю результат и оставляю мониторинг

После изменения я повторяю не только синтаксическую проверку, но и профиль нагрузки с реальными 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 внезапно удвоились, ищите замедлившийся upstream, утечку, зависшие клиенты или изменившийся профиль трафика, даже когда до потолка ещё далеко.
Порядок действий: Как проверяю результат и оставляю мониторинг — схема
Порядок действий: Как проверяю результат и оставляю мониторинг. Открыть схему в полном размере

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

Достаточно ли добавить только 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 сервиса; на отказоустойчивом контуре его делают по узлам.

Столкнулись с похожей задачей? Обращайтесь — решим

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

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 💬 MAX: +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи