Если веб‑сервис работает под нагрузкой, рано или поздно вы встретите «Too many open files», «EMFILE», «ENFILE» или внезапные сбои вотчеров на файловой системе. Все эти симптомы упираются в лимиты дескрипторов и подсистему fs.inotify. В этой статье систематизируем, откуда берутся ограничения, как их измерить и безопасно поднять для Nginx, приложений (PHP‑FPM, Node.js) и баз данных (MySQL/MariaDB, PostgreSQL).
Что такое file descriptors и почему они заканчиваются
Дескрипторы файлов (file descriptors, FD) — это абстракция ядра для «открытых вещей»: файлы, сокеты, pipe, epoll, inotify и т. д. Каждый процесс имеет собственный предел открытых дескрипторов — это RLIMIT_NOFILE (часто называют просто nofile). Есть и системные пределы, например глобальный fs.file-max, регулирующий сколько дескрипторов ядро готово обслуживать одновременно.
Веб‑нагрузка быстро съедает FD: каждая входящая TCP‑сессия — это сокет; обратные прокси держат активные апстрим‑коннекты; базы данных открывают множество файлов таблиц и журналов; логгеры и метрики создают свои дескрипторы. Добавьте сюда вотчеры fs.inotify — и лимиты перестают быть «где‑то там» теоретическими.
Где живут лимиты: уровни и источники
- Ядро:
fs.file-max,/proc/sys/fs/nr_open, fs.inotify.* (ниже разберём). - Per‑process:
RLIMIT_NOFILE— лимит «открытых файлов» на процесс. - Запуск через systemd: директива
LimitNOFILE=в unit‑файле. - Shell‑сессия:
ulimit -n(работает для текущего процесса и его потомков). - PAM limits:
/etc/security/limits.confиlimits.dдля интерактивных логинов, если включёнpam_limits. - Настройки самих приложений: Nginx
worker_rlimit_nofile, PHP‑FPMrlimit_files, MySQLopen_files_limitи т. п.
Ключевая особенность: сервисы, запущенные через systemd, не читают /etc/security/limits.conf. Для них источником истины является unit: LimitNOFILE= и глобальные DefaultLimit* из /etc/systemd/system.conf (лучше настраивать per‑service).
На обычном хостинге провайдер задаёт лимиты. Полный контроль над sysctl и unit‑файлами вы получаете на своём VDS.

Быстрая диагностика текущих лимитов
Проверьте лимит в текущей shell:
ulimit -n
Посмотрите фактические лимиты конкретного процесса (пример для Nginx master):
cat /proc/$(pgrep -o nginx)/limits
Глобальные значения ядра:
cat /proc/sys/fs/file-max
cat /proc/sys/fs/nr_open
sysctl fs.inotify.max_user_watches fs.inotify.max_user_instances fs.inotify.max_queued_events
Сколько дескрипторов реально занято у процесса:
lsof -nP -p $(pgrep -o nginx) | wc -l
Краткая сводка по сокетам:
ss -s
Если Nginx пишет «accept4: Too many open files» — почти наверняка не хватает
RLIMIT_NOFILEдля воркеров. Если MySQL жалуется на открытие таблиц — проверьтеopen_files_limitи лимиты systemd для службы СУБД.
fs.inotify: когда утыкается наблюдение за файлами
Inotify позволяет процессам подписываться на события файловой системы. Большинство «вотчеров» (деплой‑демоны, агенты логов, сборщики метрик, билд‑инструменты) используют именно его. Лимиты:
fs.inotify.max_user_watches— сколько «наблюдений» на пользователя.fs.inotify.max_user_instances— сколько inotify‑инстансов на пользователя.fs.inotify.max_queued_events— глубина очереди событий.
Симптомы ухода в лимиты: отвалившиеся вотчеры, «No space left on device» при попытке добавить новое наблюдение, повышенные задержки обработки изменений.
План действий: как безопасно поднять лимиты
- Измерьте текущее потребление FD и inotify‑вотчей у процессов.
- Задайте целевой запас исходя из пиковых нагрузок + 30–50% headroom.
- Поднимите системные лимиты через sysctl и перезапустите сервисы с обновлённым
LimitNOFILE. - Проверьте логи на отсутствие ошибок и повторно снимите метрики использования FD.
Коротко: в 80% случаев достаточно поднять
fs.file-max,LimitNOFILEу systemd‑юнитов и, при необходимости,fs.inotify.max_user_watches. Не забывайте, что настройки в конфиге приложения могут дополнительно ограничивать открытые файлы.
Настройка на уровне ядра (sysctl)
Создайте отдельный файл с параметрами, чтобы не трогать sysctl.conf напрямую:
# /etc/sysctl.d/99-fd-inotify.conf
fs.file-max = 1000000
fs.inotify.max_user_watches = 1048576
fs.inotify.max_user_instances = 1024
fs.inotify.max_queued_events = 32768
Примените изменения:
sysctl --system
Пояснения:
fs.file-max— общий «пул» дескрипторов. Значение 1M подходит большинству средних инсталляций; для нагруженных систем используйте миллионы, ориентируясь на RAM.- Каждый inotify‑watch потребляет память (сотни байт плюс накладные расходы). Миллионы вотчей — это гигабайты RAM. Не завышайте без нужды.
systemd: лимиты per‑service
Для сервиса, например Nginx:
systemctl edit nginx
И добавьте в открывшийся drop‑in:
[Service]
LimitNOFILE=200000
Перечитайте конфигурацию и перезапустите:
systemctl daemon-reload
systemctl restart nginx
Проверьте фактический лимит у процесса:
cat /proc/$(pgrep -o nginx)/limits | grep -i open
Если нужно глобально поднять дефолт для всех будущих unit‑файлов, используйте /etc/systemd/system.conf с DefaultLimitNOFILE=, но лучше контролировать на уровне каждого сервиса.
Nginx: связь nofile, worker_connections и ошибки
Nginx ограничен не только RLIMIT_NOFILE, но и собственными настройками:
events.worker_connections— максимальные соединения на воркер.worker_processes— число воркеров.worker_rlimit_nofile— значение дляRLIMIT_NOFILEworker‑процессов (независимо от лимита мастера).
Типовой фрагмент для нагруженного инстанса:
worker_processes auto;
worker_rlimit_nofile 200000;
events {
worker_connections 65535;
multi_accept on;
}
Правило большого пальца: потенциальное число одновременно открытых сокетов у Nginx примерно равно worker_processes × worker_connections плюс внутренние файлы и лог‑дескрипторы. Делайте запас в worker_rlimit_nofile и в LimitNOFILE systemd‑юнита.
Ошибки вида «accept4: Too many open files» или 502/504 под нагрузкой часто исчезают после корректного увеличения лимитов и настройки keepalive на апстримах.

PHP‑FPM и Node.js
PHP‑FPM
Два места, где упираетесь в лимиты:
- Лимит systemd у службы php‑fpm.
- Опциональный параметр пула
rlimit_files.
Пример drop‑in для systemd:
[Service]
LimitNOFILE=131072
В пуле FPM (например, /etc/php/8.2/fpm/pool.d/www.conf):
rlimit_files = 131072
После правок перезапустите FPM и убедитесь, что в /proc/<pid>/limits видите увеличенные значения.
Node.js
Node‑приложения часто падают в EMFILE при массовых операциях с сокетами или при запущенных вотчерах. Для сервисов под systemd достаточно поднять LimitNOFILE; для локальных дев‑окружений — ulimit -n. Если используете менеджер процессов (PM2, systemd), контролируйте лимиты там, где стартует процесс.
Базы данных: MySQL/MariaDB и PostgreSQL
MySQL/MariaDB
Ограничения на открытые файлы регулируются параметром open_files_limit и связаны с table_open_cache. Но верхняя планка — это всё равно RLIMIT_NOFILE процесса СУБД. Последовательность:
- Поднимите
LimitNOFILEв unit‑файле службы MySQL/MariaDB. - В
my.cnfзадайте целевоеopen_files_limitи соответствующийtable_open_cache. - Перезапустите службу и проверьте значения через SHOW VARIABLES.
# /etc/mysql/my.cnf
[mysqld]
open_files_limit = 200000
table_open_cache = 8192
Проверка:
mysql -e "SHOW VARIABLES LIKE 'open_files_limit';"
Симптомы нехватки: «Can't open file», частые закрытия/открытия таблиц, рост latency на запросах из‑за постоянного вращения дескрипторов.
PostgreSQL
Postgres читает RLIMIT_NOFILE от системы и сам не имеет отдельного параметра для «open files». Действуйте через systemd:
[Service]
LimitNOFILE=131072
Важно помнить, что дескрипторы потребляют не только backend‑процессы коннектов, но и чекпойнты, автовакуум и расширения. Учитывайте суммарное число процессов и возможные пики коннектов.
Если вы шифруете соединения СУБД, отдельно проверьте TLS‑настройки и цепочки сертификатов — это тоже дескрипторы на сокеты и ключи. Подробности см. в разборе TLS для MySQL и PostgreSQL: CA и настройки.
Ошибки и лог‑сообщения: что искать
- Nginx: «accept() failed (24: Too many open files)», «upstream prematurely closed connection».
- PHP‑FPM: «failed to open error_log», «Too many open files» при форках воркеров.
- Node.js: «EMFILE: too many open files, watch».
- MySQL/MariaDB: «Can't open file», «Too many open files» при пиках.
- PostgreSQL: «too many open files in system», «could not open file».
- Inotify: «No space left on device» при добавлении watch.
Проверяем фактическое потребление и запас
Снимите срез во время пика:
PID=$(pgrep -o nginx)
lsof -nP -p $PID | wc -l
cat /proc/$PID/limits | grep -i open
Подготовьте отчёт: текущий максимум FD, пиковое значение, целевой запас, какие юниты и sysctl были изменены. Это поможет в change‑management и при последующих аудитах.
Память и безопасность: не задирайте бесконечно
Каждый дескриптор и inotify‑watch потребляют память ядра. Миллионы FD в системе без реальной потребности — это лишняя RAM и потенциально более сложные таблицы поиска, что может влиять на латентность в крайних случаях. Для продакшена используйте здравый запас и мониторинг.
- Добавьте алерты по занятости FD на процесс и по системе (экспорт из
/proc/sys/fs/file-nrи/proc/<pid>/fd). - Лимиты повышайте итеративно и только после измерений.
В тему оптимизации памяти на серверах приложений и баз данных посмотрите нашу статью про оптимизацию THP и HugePages для PHP и баз данных.
PAM limits и интерактивные сессии
Если админ‑скрипты или демоны запускаются из crontab или вручную через shell, убедитесь, что pam_limits включён, а в limits.conf задан разумный nofile для нужных пользователей или групп. Но помните: для systemd‑сервисов это не работает — им настраиваем LimitNOFILE в unit‑файлах.
Контейнерные окружения (кратко)
В контейнерах лимиты по умолчанию часто ниже. Управляйте ими на уровне рантайма (параметры ulimit) и внутри гостевой ОС, если там запускается systemd. Также обращайте внимание на передачу sysctl‑параметров ядра — не все можно менять изнутри контейнера.
Чек‑лист внедрения
- Соберите метрики: пиковые FD и inotify‑watch per process, по системе.
- Повысьте
fs.file-maxи fs.inotify.* через/etc/sysctl.d/, применитеsysctl --system. - Задайте
LimitNOFILEв drop‑in для каждого сервиса: Nginx, PHP‑FPM, Node.js, базы данных. - Для Nginx настройте
worker_rlimit_nofileиworker_connectionsс запасом. - Для MySQL/MariaDB проверьте
open_files_limitиtable_open_cache; для PostgreSQL — только systemd‑лимит. - Перезапустите сервисы, подтвердите новые лимиты через
/proc/<pid>/limits. - Включите мониторинг и алерты по занятости FD и очередям inotify.
Итоги
Лимиты дескрипторов — это не «тайная магия», а три слоя: ядро (sysctl), менеджер служб (systemd) и конкретные приложения. Понимание того, где именно вы упираетесь — в RLIMIT_NOFILE, в worker_connections, в open_files_limit или в fs.inotify — позволяет быстро устранить «Too many open files», стабилизировать Nginx и приложения, а также избежать скрытых деградаций баз данных. Настраивайте лимиты осознанно, подтверждайте их метриками и оставляйте разумный запас.


