Ошибка Too many open files — одна из самых неприятных в эксплуатации: она появляется «вдруг», бьёт по многим сервисам сразу (веб, база, агенты), а лечение часто превращают в магию: «поднимем ulimit и забудем». На практике важно понять, какой именно лимит вы упёрли, какой процесс его съел, и нет ли file descriptor leak (утечки дескрипторов).
Ниже — рабочая схема диагностики и настройки в Linux: ulimit/nofile, systemd LimitNOFILE и системные лимиты /proc/sys/fs/file-max. Плюс конкретика для Nginx (worker_connections) и MariaDB (open_files_limit), и где реально возможен «reload без простоя», а где нет.
Что именно означает Too many open files
В Linux почти всё — «файл»: сетевые сокеты, файлы логов, FIFO, eventfd, inotify-дескрипторы, unix-сокеты. Каждый открытый объект занимает файловый дескриптор (fd). Когда процесс пытается открыть новый fd, но упирается в лимит, он получает ошибку уровня ядра: обычно EMFILE (достигнут лимит процесса) или ENFILE (в системе закончился общий пул fd).
Поэтому полезно сразу разделить два класса проблем:
- Пер-процессный лимит: ограничение
RLIMIT_NOFILEдля конкретного процесса/сервиса. В быту это «ulimit -n». - Системный лимит: общее ограничение ядра на число открытых файлов в системе —
/proc/sys/fs/file-max. Если упёрлись в него, страдают многие процессы.
Поднять лимит — это стабилизация, но не финал. Если есть утечка fd, сервис снова упрётся в потолок, просто позже — и иногда гораздо больнее.
Быстрая диагностика: какой лимит упёрли
1) Проверяем системную картину: file-max и текущее потребление
Смотрим системный максимум и текущую статистику:
cat /proc/sys/fs/file-max
cat /proc/sys/fs/file-nr
Файл /proc/sys/fs/file-nr обычно содержит три числа: «выделено», «свободно» (на новых ядрах часто 0) и «максимум». Если первое число близко к третьему — проблема системная.
Для быстрого чтения можно вывести понятнее:
awk '{print "allocated=" $1, "unused=" $2, "max=" $3}' /proc/sys/fs/file-nr
2) Ищем виновника по количеству fd
Чаще всего один процесс «съедает» львиную долю fd. Быстрый способ — посмотреть топ процессов по количеству дескрипторов:
for pid in /proc/[0-9]*; do p=${pid##*/}; c=$(ls -1 /proc/$p/fd 2>/dev/null | wc -l); echo "$c $p"; done | sort -nr | head
Дальше берём PID из топа и смотрим детали:
ps -p 1234 -o pid,ppid,user,cmd --no-headers
ls -l /proc/1234/fd | head
3) Проверяем лимиты конкретного процесса
Частая ловушка: вы поднимаете лимиты «в SSH», но сервис запущен через systemd и живёт с другими значениями. Реальный лимит процесса смотрите так:
cat /proc/1234/limits | sed -n '1p;/open files/Ip'
В выводе ищите строку Max open files — это фактический лимит nofile для данного PID.
4) Диагностика через lsof: что именно открыто
Когда PID найден, важно понять тип нагрузки: сокеты, файлы, «висящие» удалённые файлы, утечки. Команды:
lsof -p 1234 | head
lsof -p 1234 | wc -l
Сводка по типам:
lsof -p 1234 | awk 'NR>1 {print $5}' | sort | uniq -c | sort -nr | head
Отдельно полезно искать ситуацию, когда файл удалён, но процесс продолжает держать fd (типично для лог-ротации):
lsof -p 1234 | grep -F '(deleted)' | head
Если таких записей много — лимит можно «проесть» даже без видимых файлов на диске, и df это не покажет.

Почему ulimit «не работает»: оболочка, PAM и systemd
ulimit nofile в интерактивной сессии
ulimit -n показывает лимит для текущей оболочки и процессов, которые вы из неё запустите:
ulimit -n
ulimit -Hn
ulimit -Sn
Где -H — hard limit, -S — soft limit. Процесс может поднять soft до hard, но не выше hard (без привилегий).
limits.conf (PAM): полезно для SSH, но не источник истины для systemd
Файлы /etc/security/limits.conf и /etc/security/limits.d/*.conf применяются в PAM-сессиях (SSH/login). Для демонов, стартующих через systemd, это часто не работает ожидаемо. Отсюда классическая ситуация: «в SSH ulimit высокий», а сервис всё равно падает.
systemd LimitNOFILE — основной рычаг для сервисов
Если сервис управляется systemd, корректнее задавать лимиты через unit-файл:
systemctl show nginx -p LimitNOFILE
systemctl show mariadb -p LimitNOFILE
Если значение недостаточное — делаем drop-in override, не редактируя пакетный unit напрямую:
systemctl edit nginx
И добавляем:
[Service]
LimitNOFILE=200000
Далее применяем:
systemctl daemon-reload
systemctl restart nginx
Нюанс: изменение LimitNOFILE для уже работающего процесса обычно требует перезапуска сервиса. Для части стеков можно добиться «почти без простоя» через graceful reload, но лимит fd всё равно зависит от того, какие процессы реально перезапускаются (master/worker модель).
Глобальные лимиты systemd (DefaultLimitNOFILE)
Если вы хотите поднять дефолт для большинства сервисов, используйте настройки менеджера systemd:
grep -nE 'DefaultLimitNOFILE' /etc/systemd/system.conf /etc/systemd/user.conf 2>/dev/null
Задаётся, например, так:
sed -n '1,120p' /etc/systemd/system.conf
DefaultLimitNOFILE=65535
После изменения требуется перезапуск systemd как менеджера, поэтому чаще безопаснее задавать лимит точечно на нужный сервис через drop-in.
Системный лимит /proc/sys/fs/file-max и как его менять правильно
Если вы упёрлись в /proc/sys/fs/file-max, увеличение per-process лимита не поможет: у ядра просто закончился общий пул.
Проверить текущий:
sysctl fs.file-max
Временно поднять до перезагрузки:
sysctl -w fs.file-max=2097152
Постоянно — через /etc/sysctl.d/:
printf '%s
' 'fs.file-max = 2097152' > /etc/sysctl.d/99-file-max.conf
sysctl --system
После изменения снова смотрите /proc/sys/fs/file-nr, чтобы убедиться, что ушли от потолка и проблема не повторится при росте нагрузки.
Nginx: worker_connections, лимиты fd и что можно сделать без простоя
Как связаны worker_connections и nofile
В Nginx каждый worker держит соединения (сокеты) и открывает файлы (статика, кэш, логи). Параметр worker_connections ограничивает число одновременных соединений на один worker. Но даже если worker_connections высокий, реальный предел часто упирается в nofile процесса.
Проверьте, какой лимит видит master:
cat /proc/$(cat /run/nginx.pid)/limits | sed -n '/open files/Ip'
Правильная настройка: systemd + nginx.conf
База — поднять LimitNOFILE в systemd unit. Дополнительно в конфиге Nginx полезно задать:
worker_rlimit_nofile 200000;
Важно: worker_rlimit_nofile не создаёт лимит «из воздуха» — он может поднять лимит воркеров только в рамках того, что разрешено master-процессу. Поэтому первичен systemd.
Reload без простоя: что реально возможно
Nginx умеет graceful reload конфигурации:
nginx -t
systemctl reload nginx
Это не «restart сервиса» в классическом смысле: master перечитает конфиг и плавно перезапустит воркеры. Но если цель — увеличить LimitNOFILE, одного reload обычно недостаточно: лимит задаётся на уровне процесса и гарантированно применяется при запуске master. Практический вывод: лимиты держите с запасом заранее, а если упёрлись — планируйте короткое окно на перезапуск.
Если ваш Nginx крутится на виртуальном хостинге, учтите, что менять системные лимиты ядра вы, как правило, не сможете — остаётся оптимизировать приложение/соединения и настраивать то, что доступно в рамках окружения. На VDS вы контролируете и systemd, и sysctl, и это сильно упрощает борьбу с fd-лимитами.
MariaDB/MySQL: open_files_limit и взаимосвязь с systemd
У MariaDB есть собственный параметр open_files_limit (часто видно в логах попытку его установить). Но итоговое значение всегда ограничено OS limit (nofile) для процесса mariadbd/mysqld.
Проверить лимиты процесса:
pid=$(pidof mariadbd 2>/dev/null || pidof mysqld)
cat /proc/$pid/limits | sed -n '/open files/Ip'
Проверить, что думает сама БД:
mysql -e "SHOW VARIABLES LIKE 'open_files_limit';"
Если значение ниже ожидаемого, решать нужно «снизу вверх»:
- Поднять
LimitNOFILEдля сервиса MariaDB в systemd. - Проверить/настроить
open_files_limitв конфигурации БД (если это действительно требуется). - Контролировать рост открытых таблиц/файлов (особенно при множестве таблиц, соединений и временных файлов).
С перезапуском БД аккуратнее: «reload without restart» обычно ограничен перечитыванием части параметров, но лимиты fd так не поднимутся. Планируйте обслуживание или используйте сценарии высокой доступности, если они уместны.
Как отличить нехватку лимита от утечки file descriptor leak
Классический признак утечки: число fd у процесса монотонно растёт и не возвращается вниз даже при падении нагрузки.
Мониторим рост fd «в лоб»
pid=1234
watch -n 2 "ls -1 /proc/$pid/fd 2>/dev/null | wc -l"
Если значение растёт постоянно — это повод копать глубже.
Смотрим, какие именно fd множатся
Например, вы подозреваете сокеты:
lsof -p 1234 | awk 'NR>1 {print $8}' | head
Или вы увидели тысячи одинаковых файлов/путей (типично для логов/временных файлов):
lsof -p 1234 | awk 'NR>1 {print $9}' | sort | uniq -c | sort -nr | head
Если доминируют записи (deleted) — проверьте ротацию логов и правильный сигнал/команду переоткрытия логов для конкретного сервиса.

Частые причины утечек и «ложных» утечек
- Неправильная logrotate-стратегия: файл переименовали/удалили, процесс продолжает писать в старый fd.
- Долгоживущие keep-alive соединения: fd растут при резком росте клиентов, но это может быть нормой, если лимит был слишком низкий.
- Баги/утечки в приложении: fd растут даже без трафика (часто в кастомных воркерах, парсерах очередей, интеграциях).
- Сторонние агенты: индексаторы, бэкап-клиенты, сборщики логов, открывающие много файлов параллельно.
Если хотите глубже разобраться с лимитами и влиянием inotify, держите отдельный разбор: лимиты nofile и inotify в Linux: где смотреть и как не поймать сюрпризы.
Практический чек-лист: что делать в момент инцидента
- Понять масштаб: системный потолок или один процесс? Смотрите
/proc/sys/fs/file-nrи топ PID по fd. - Определить лимит процесса:
/proc/PID/limits. - Понять состав fd:
lsofпо процессу, ищите(deleted), одинаковые пути, лавину сокетов. - Быстро стабилизировать: временно поднять
fs.file-max(если системный лимит) илиLimitNOFILEи аккуратно перезапустить сервис; при утечке — ограничить параллелизм/нагрузку и готовить фикс. - После пожара: добавить мониторинг «fd per process» и алерты до того, как лимит будет близко.
Тонкая настройка: сколько ставить и где можно «перестараться»
Ставить «миллион» бездумно не всегда лучшая идея. Высокие лимиты увеличивают потенциальный ущерб от утечки: процесс сможет сожрать больше ресурсов, а падение станет более массовым и сложнее локализуемым.
Рабочая практика такая:
- Задавать разумный запас
LimitNOFILEточечно для веба, базы, прокси, очередей. - Держать системный
fs.file-maxвыше суммарных ожидаемых пиков. - Сопоставлять лимиты с конфигурацией: в Nginx —
worker_connectionsи числом workers; в БД — с реальной файловой активностью. - Если fd растут без нагрузки — это почти всегда утечка или проблема с переоткрытием логов после ротации.
Мини-памятка команд (для копипаста)
# Системный максимум и текущее потребление
sysctl fs.file-max
cat /proc/sys/fs/file-nr
# Топ процессов по количеству fd
for pid in /proc/[0-9]*; do p=${pid##*/}; c=$(ls -1 /proc/$p/fd 2>/dev/null | wc -l); echo "$c $p"; done | sort -nr | head
# Лимит nofile у процесса
cat /proc/1234/limits | sed -n '/open files/Ip'
# Что открыто у процесса
lsof -p 1234 | wc -l
lsof -p 1234 | grep -F '(deleted)' | head
# systemd: посмотреть и поднять LimitNOFILE
systemctl show nginx -p LimitNOFILE
systemctl edit nginx
systemctl daemon-reload
Итоговая мысль простая: Too many open files лечится не одной цифрой в ulimit. Это всегда про согласованность трёх уровней — конфигурации сервиса (например, Nginx/MariaDB), лимитов процесса (systemd и LimitNOFILE) и лимитов ядра (fs.file-max) — плюс дисциплина диагностики через lsof и контроль утечек fd.


