Выберите продукт

Linux: Too many open files — ulimit, systemd LimitNOFILE и диагностика утечек дескрипторов

Ошибка Too many open files обычно связана с лимитами fd или утечкой дескрипторов. Разберём ulimit nofile, systemd LimitNOFILE и fs.file-max, найдём виновника через /proc и lsof, и настроим Nginx/MariaDB без лишнего простоя.
Linux: Too many open files — ulimit, systemd LimitNOFILE и диагностика утечек дескрипторов

Ошибка 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 это не покажет.

Схема быстрой диагностики: /proc file-nr и топ процессов по числу открытых дескрипторов

Почему 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 модель).

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

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

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Как отличить нехватку лимита от утечки 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) — проверьте ротацию логов и правильный сигнал/команду переоткрытия логов для конкретного сервиса.

Пример lsof: множество сокетов и файлов со статусом (deleted) при проблемах с ротацией логов

Частые причины утечек и «ложных» утечек

  • Неправильная logrotate-стратегия: файл переименовали/удалили, процесс продолжает писать в старый fd.
  • Долгоживущие keep-alive соединения: fd растут при резком росте клиентов, но это может быть нормой, если лимит был слишком низкий.
  • Баги/утечки в приложении: fd растут даже без трафика (часто в кастомных воркерах, парсерах очередей, интеграциях).
  • Сторонние агенты: индексаторы, бэкап-клиенты, сборщики логов, открывающие много файлов параллельно.

Если хотите глубже разобраться с лимитами и влиянием inotify, держите отдельный разбор: лимиты nofile и inotify в Linux: где смотреть и как не поймать сюрпризы.

Практический чек-лист: что делать в момент инцидента

  1. Понять масштаб: системный потолок или один процесс? Смотрите /proc/sys/fs/file-nr и топ PID по fd.
  2. Определить лимит процесса: /proc/PID/limits.
  3. Понять состав fd: lsof по процессу, ищите (deleted), одинаковые пути, лавину сокетов.
  4. Быстро стабилизировать: временно поднять fs.file-max (если системный лимит) или LimitNOFILE и аккуратно перезапустить сервис; при утечке — ограничить параллелизм/нагрузку и готовить фикс.
  5. После пожара: добавить мониторинг «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.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: как исправить Device is busy у Docker network, volume и namespace OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Device is busy у Docker network, volume и namespace

Если Docker на Debian или Ubuntu отвечает Device is busy при удалении сети, тома или namespace, причина обычно в живом процессе, о ...
Debian/Ubuntu: как исправить Host key verification failed в Ansible при смене IP OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Host key verification failed в Ansible при смене IP

Ошибка Host key verification failed в Ansible на Debian и Ubuntu обычно возникает после переустановки сервера, смены IP или повтор ...
Debian/Ubuntu: duplicate address detected, DAD failed IPv6 — причины и исправление OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: duplicate address detected, DAD failed IPv6 — причины и исправление

Сообщения duplicate address detected и DAD failed в Debian/Ubuntu означают, что IPv6-адрес не прошёл проверку уникальности в локал ...