Debian 13 «Занадто багато відкритих файлів»: правильне виправлення через systemd (не лише ulimit)

Було корисно?

«Занадто багато відкритих файлів» — це одне з тих повідомлень, що виглядає як просте повернення ручки, а в підсумку перетворюється на післяобіднє марево неправильних напрямків. Ви виконуєте ulimit -n 1048576, воно показує велике число, яке ви попросили, ви перезапускаєте сервіс, а він все одно падає під навантаженням. Ваш термінал каже «виправлено». Продакшн каже «приємно спробувати».

На Debian 13 правильне виправлення зазвичай не полягає у додаткових shell-хитрощах. Це systemd. Конкретно: правильний рівень systemd, правильний юніт і крок перевірки, що не брешуть вам. Зробімо це правильно — так, як ви б хотіли в 02:00 під наглядом клієнтів.

Що насправді означає «Занадто багато відкритих файлів» (і чого це не означає)

В Linux «Занадто багато відкритих файлів» зазвичай означає одну з двох помилок:

  • EMFILE: процес досягнув свого пер- процесного ліміту файлових дескрипторів (RLIMIT_NOFILE). Це те, що ви виправляєте через systemd LimitNOFILE= (або еквівалент). Це персонально. Один процес перевищив бюджет.
  • ENFILE: система досягла глобального ліміту таблиці файлів. Це рідше в сучасних ядрах, але трапляється у патологічних випадках. Це спільне. Весь хост перевищив бюджет.

Також: «відкриті файли» — неточна фраза. Йдеться про файлові дескриптори. Ці дескриптори можуть позначати звичайні файли, каталоги, сокети, pipe, eventfd, signalfd, inotify-спостереження, epoll-інстанси та кілька інших речей, які стають зрозумілими після довгого робочого тижня.

Дві наслідки:

  1. Ви можете вичерпати FD, не відкривши жодного лог-файлу. Сервіси з великим числом з’єднань помирають саме так дуже часто.
  2. Бездумне підвищення лімітів може приховати витоки, маскувати погану обробку з’єднань і перемістити помилку в іншу підсистему (наприклад пам’ять, керування ядром або ваш бекенд зберігання).

Цитата, що досі актуальна в операційному середовищі, перефразована, бо я не став би ризикувати на точну цитату: paraphrased idea«Надія — не стратегія.» (часто приписують інженерам типу Gene Kranz і це часто повторюють у роботі з надійністю). В цьому контексті: не сподівайтеся, що ваш ulimit щось зробив. Доведіть це, потім виправте на рівні, який дійсно запускає сервіс.

Жарт #1: «Занадто багато відкритих файлів» — це спосіб Linux сказати, що ваш процес має проблеми з відносинами. Він відкриває речі і відмовляється їх закривати.

Швидкий план діагностики

Це секція «зупиніться і зробіть ці три перевірки». Вона впорядкована так, як я дію, коли виклик гарячий.

Перше: визначте, чи це пер-процесний (EMFILE) чи системний (ENFILE)

  • Шукайте точну помилку у логах (логи додатку, journal). EMFILE/ENFILE вирішальний.
  • Якщо ви не можете отримати точний errno, швидко перевірте використання FD на процес і RLIMIT (див. завдання нижче).

Друге: перевірте реальний RLIMIT_NOFILE запущеного сервісу (не вашого shell)

  • Отримайте PID головного процесу сервісу.
  • Прочитайте /proc/<pid>/limits. Цей файл — реальність.

Третє: знайдіть, хто встановив ліміт (systemd unit, drop-in, defaults, PAM)

  • Якщо процес запущений через systemd, юніт (та системні defaults) перемагають.
  • Якщо це сервіс користувача, менеджер користувача має свої defaults.
  • PAM-ліміти часто впливають на інтерактивні логіни й деякі сервіси, але не типові системні сервіси.

Якщо ви зробите тільки одну річ сьогодні: припиніть довіряти виводу ulimit з вашого логін-shell як доказу, що ваш демон має такий ліміт. Саме так ви «виправляєте» речі тричі.

Стек лімітів у Debian 13: хто перемагає і чому

В Linux є ресурсні ліміти (rlimits). systemd може встановлювати їх для сервісів. PAM може встановлювати їх для сесій входу. Shell може встановлювати їх для дочірніх процесів. Контейнери можуть додавати свої обмеження. І ядро має системні максимуми.

Ось практична ієрархія, на яку більшість натрапляє:

  1. Системні максимуми та загальні таблиці (наприклад, fs.file-max). Це жорсткі межі та спільні ресурси.
  2. Пер-процесні rlimits (RLIMIT_NOFILE). Кожному процесу присвоєні «soft» та «hard» ліміти. Soft-ліміт — це те, що ви досягаєте. Hard — стеля, яку можна підняти без привілеїв.
  3. Налаштування менеджера сервісів systemd встановлюють rlimits під час exec і застосовуються до дерева процесів сервісу.
  4. Сесії користувача (PAM + user manager) можуть встановлювати rlimits для інтерактивних shell та сервісів користувача.
  5. Shell ulimit — це лише погляд shell і те, що він передає своїм дочірнім процесам. Це нічого не робить для вже запущених демонів.

Ключова реальність Debian 13: більшість серверних демонів запускаються systemd. Це означає, що файл юніта та defaults systemd важать більше за все, що ви вводите в shell.

Також systemd — це не просто інітаційний скрипт. Це супервізор процесів, що може встановлювати ліміти, ізолювати ресурси й контролювати файлові дескриптори так, що ваші припущення можуть не відповідати дійсності. Якщо ваша ментальна модель — «/etc/security/limits.conf контролює все», ви витратите час даремно.

Правильне виправлення через systemd: LimitNOFILE, defaults та drop-ins

Є три розумні способи підвищити ліміти FD для systemd-сервісу на Debian 13. Обирайте залежно від радіусу ураження.

Опція A (рекомендовано): встановіть LimitNOFILE у drop-in для конкретного сервісу

Це хірургічне вирішення. Воно змінює лише один юніт. Воно переживає оновлення пакетів. Його легко переглядати.

Створіть drop-in:

cr0x@server:~$ sudo systemctl edit nginx.service

Потім додайте:

cr0x@server:~$ sudo tee /etc/systemd/system/nginx.service.d/limits.conf >/dev/null <<'EOF'
[Service]
LimitNOFILE=262144
EOF

Перезавантажте та рестартуйте:

cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart nginx.service
cr0x@server:~$ systemctl status nginx.service --no-pager
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
    Drop-In: /etc/systemd/system/nginx.service.d
             └─limits.conf
     Active: active (running)

Якщо ви не бачите Drop-In у списку, ваш drop-in розміщено не там або ви забули daemon-reload.

Опція B: змінити системні defaults для всіх системних сервісів (сильний удар)

Ви можете задати дефолтні ліміти у /etc/systemd/system.conf та/або /etc/systemd/user.conf:

cr0x@server:~$ sudo sed -n '1,120p' /etc/systemd/system.conf
#  This file is part of systemd.
#DefaultLimitNOFILE=1024:524288

Розкоментуйте і встановіть, наприклад:

cr0x@server:~$ sudo perl -0777 -pe 's/^#?DefaultLimitNOFILE=.*/DefaultLimitNOFILE=65536:1048576/m' -i /etc/systemd/system.conf

Потім:

cr0x@server:~$ sudo systemctl daemon-reexec

Попередження з позицією: Опція B — це шлях, як ви випадково «виправите» один сервіс, давши всім сервісам дозвіл шкодити хосту новими способами. Використовуйте її лише коли маєте політику для флоту і причину.

Опція C: налаштувати власні ліміти програми (якщо стосується)

Деякі демони мають власні перемикачі лімітів (наприклад через worker_rlimit_nofile в nginx), які треба узгодити з RLIMIT_NOFILE. Вони не замінюють ліміти systemd; вони накладаються зверху. Якщо systemd обмежує 8192, конфіг вашого додатку може просити 100k хоч скільки, але отримає 8192.

Отже правильний порядок: спочатку встановіть systemd LimitNOFILE, потім налаштуйте рівень додатку, щоб ним користуватися.

А як щодо /etc/security/limits.conf?

Він не марний, просто часто не має відношення до системних сервісів. /etc/security/limits.conf застосовується PAM у контекстах входу. Якщо ви запускаєте демон через systemd під час завантаження, PAM тут ні до чого. Debian 13 у цьому випадку не унікальний; сучасний Linux чесно багаторівневий.

Жарт #2: ulimit як кричати бажану заробітну плату в коридорі. Приємно, але бухгалтерія все одно робить те, як налаштовано.

Перевірка, яка не брешe: /proc, systemctl і живі процеси

Якщо ви вхопите одну операційну звичку з цього матеріалу — візьміть цю: перевіряйте ліміти у запущеному процесі, а не в shell, який випадково у вас на екрані.

Три надійні кути:

  • /proc/<pid>/limits показує поточні soft/hard ліміти для цього процесу.
  • systemctl show може показати властивості юніта та ефективні налаштування (але все одно перевіряйте в /proc).
  • Підрахунок відкритих FD через /proc/<pid>/fd каже, чи наближаєтеся ви до обмеження, чи є витік, чи пікове навантаження.

І пам’ятайте: сервіси часто мають кілька процесів (master + workers). Той, що падає, може бути не тим, на якого ви дивилися. Перевіряйте процес, що фактично випускає EMFILE.

Практичні завдання (команди + що означає вивід + рішення, які ви приймаєте)

Це завдання з поля. Запускайте їх на Debian 13. Кожне містить: команду, приклад виводу, інтерпретацію та рішення.

Завдання 1: Підтвердити код помилки в journal

cr0x@server:~$ sudo journalctl -u nginx.service -n 50 --no-pager
Dec 28 10:12:01 server nginx[1423]: accept4() failed (24: Too many open files)
Dec 28 10:12:01 server nginx[1423]: worker_connections are not enough

Значення: errno 24 — це EMFILE. Це пер-процесний RLIMIT_NOFILE, не глобальні таблиці ядра.

Рішення: Зосередьтеся на LimitNOFILE для процесів nginx і на власних налаштуваннях worker у nginx. Поки що не чіпайте fs.file-max.

Завдання 2: Знайти MainPID сервісу

cr0x@server:~$ systemctl show -p MainPID --value nginx.service
1423

Значення: PID 1423 — головний процес, який відслідковує systemd.

Рішення: Використовуйте цей PID для початкової перевірки, але також перевірте PIDs воркерів.

Завдання 3: Прочитати реальний RLIMIT_NOFILE з /proc

cr0x@server:~$ sudo awk '/Max open files/ {print}' /proc/1423/limits
Max open files            8192                 8192                 files

Значення: Soft=8192, Hard=8192. Ви дістанете EMFILE приблизно при 8192 дескрипторах у цьому процесі.

Рішення: Підвищуйте це через systemd (drop-in). Shell ulimit не змінить вже запущений процес.

Завдання 4: Підтвердити, що systemd вважає ліміт таким

cr0x@server:~$ systemctl show nginx.service -p LimitNOFILE
LimitNOFILE=8192

Значення: systemd наразі застосовує 8192 до сервісу.

Рішення: Додайте drop-in із більшим LimitNOFILE, потім рестартніть сервіс.

Завдання 5: Створити і перевірити override drop-in

cr0x@server:~$ sudo systemctl edit nginx.service
cr0x@server:~$ sudo systemctl cat nginx.service
# /lib/systemd/system/nginx.service
[Unit]
Description=A high performance web server and a reverse proxy server

# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=262144

Значення: Drop-in завантажено і воно перекриває юніт.

Рішення: Перезавантажте і рестартніть. Якщо drop-in не з’являється у systemctl cat, воно не застосовано.

Завдання 6: Перезапустити і перевірити /proc ліміти

cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart nginx.service
cr0x@server:~$ systemctl show -p MainPID --value nginx.service
1777
cr0x@server:~$ sudo awk '/Max open files/ {print}' /proc/1777/limits
Max open files            262144               262144               files

Значення: Запущений процес тепер має підвищений ліміт. Це і є умова успіху.

Рішення: Перейдіть до перевірки використання/тиску: чи все ще витоки FD, чи проблема була лише в низькому капі?

Завдання 7: Порахувати поточні відкриті FD для процесу

cr0x@server:~$ sudo ls /proc/1777/fd | wc -l
412

Значення: Наразі відкрито 412 дескрипторів. Це знімок.

Рішення: Якщо це число постійно зростає, підозрівайте витік. Якщо воно стрибає під час трафіку — налаштуйте пікову конкурентність відповідно.

Завдання 8: Визначити, які дескриптори домінують

cr0x@server:~$ sudo ls -l /proc/1777/fd | head -n 12
lrwx------ 1 www-data www-data 64 Dec 28 10:20 0 -> /dev/null
lrwx------ 1 www-data www-data 64 Dec 28 10:20 1 -> /var/log/nginx/access.log
lrwx------ 1 www-data www-data 64 Dec 28 10:20 2 -> /var/log/nginx/error.log
lrwx------ 1 www-data www-data 64 Dec 28 10:20 3 -> socket:[23451]
lrwx------ 1 www-data www-data 64 Dec 28 10:20 4 -> socket:[23452]

Значення: Багато сокетів означає навантаження з’єднань. Багато звичайних файлів може означати роботу з логами/файлами, кеш або витоки в файловому вводі/виводі.

Рішення: Якщо домінують сокети — налаштуйте обработку з’єднань (backlog, keepalive, кількість воркерів). Якщо файли — шукайте витоки файлів або змініть поведінку ротації/відкриття.

Завдання 9: Перевірити пер-юзерні ліміти для інтерактивних сесій (PAM)

cr0x@server:~$ ulimit -Sn
1024
cr0x@server:~$ ulimit -Hn
1048576

Значення: Ваш логін-shell має soft 1024, hard 1048576. Це не застосовується автоматично до systemd-сервісів.

Рішення: Якщо розробники запускають навантаження вручну (не через systemd), відрегулюйте PAM-ліміти. Інакше зосередьтеся на лімітах юніта.

Завдання 10: Перевірити системний тиск файлових дескрипторів

cr0x@server:~$ cat /proc/sys/fs/file-nr
2976	0	9223372036854775807

Значення: Перше число — виділені файлові дескриптори; третє — максимальне. На багатьох системах максимум фактично величезний (або «майже нескінченний»).

Рішення: Якщо виділене близько до максимуму (на системах із реальним лімітом), у вас проблема на рівні хоста. Інакше, швидше за все, це пер-процесна проблема.

Завдання 11: Перевірити системну стелю відкритих файлів на процес (fs.nr_open)

cr0x@server:~$ cat /proc/sys/fs/nr_open
1048576

Значення: Це стеля ядра для RLIMIT_NOFILE. Ви не можете встановити пер-процесні ліміти вище цього значення.

Рішення: Якщо вам дійсно потрібно > 1,048,576 FD на процес (рідко, але можливо для проксі-боксів), потрібно підняти fs.nr_open через sysctl і впевнитися у наявності пам’яті.

Завдання 12: Підтвердити ефективні ліміти юніта після рестарту

cr0x@server:~$ systemctl show nginx.service -p LimitNOFILE
LimitNOFILE=262144

Значення: systemd вважає, що застосовує новий ліміт.

Рішення: Якщо systemctl show вказує нове значення, але /proc/<pid>/limits — ні, ви перезапустили не те, ви дивитесь не на той PID, або сервіс використовує wrapper, що виконує exec десь ще. Слідуйте за ланцюгом PID.

Завдання 13: Знайти всі PID у cgroup сервісу і перевірити одного воркера

cr0x@server:~$ systemctl show -p ControlGroup --value nginx.service
/system.slice/nginx.service
cr0x@server:~$ cat /sys/fs/cgroup/system.slice/nginx.service/cgroup.procs | head
1777
1780
1781
1782
cr0x@server:~$ sudo awk '/Max open files/ {print}' /proc/1780/limits
Max open files            262144               262144               files

Значення: Воркер успадкував той самий ліміт. Добре.

Рішення: Якщо воркери відрізняються — можливо у вас змішані шляхи запуску або exec-обгортки. Виправте шлях запуску сервісу.

Завдання 14: Ловити ріст FD з часом (проста перевірка витоку)

cr0x@server:~$ for i in 1 2 3 4 5; do date; sudo ls /proc/1777/fd | wc -l; sleep 5; done
Sun Dec 28 10:24:01 UTC 2025
412
Sun Dec 28 10:24:06 UTC 2025
418
Sun Dec 28 10:24:11 UTC 2025
430
Sun Dec 28 10:24:16 UTC 2025
446
Sun Dec 28 10:24:21 UTC 2025
462

Значення: Показник росте. Це може бути легітимне збільшення навантаження або витік.

Рішення: Якщо трафік стабільний, але FD ростуть монотонно — розслідуйте витік (застряглі upstream-сокети, файли не закриваються, баги в бібліотеках). Підняття лімітів лише купує час.

Завдання 15: Підтвердити, що сервіс не обмежений обгорткою як su/sudo чи cron

cr0x@server:~$ ps -o pid,ppid,cmd -p 1777
  PID  PPID CMD
 1777     1 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;

Значення: PPID 1 вказує, що батько — systemd (або init). Це системний сервіс, отже застосовуються ліміти systemd.

Рішення: Якщо PPID — shell, cron або wrapper, виправте метод запуску або переконайтеся, що wrapper встановлює rlimits коректно (зазвичай: припиніть так робити, використовуйте юніт).

Три корпоративні міні-історії з бойових

Міні-історія 1: інцидент через неправильне припущення

Середня SaaS-компанія запускала API-шар на Debian за реверс-проксі. Під час маркетингового заходу запити почали відмовляти зі спорадичними 502. Інженер на чергуванні зробив звичне: зайшов по SSH, запустив ulimit -n, побачив велике число (вони його налаштували місяці тому), і вирішив «це не ліміт FD». Вони шукали CPU і мережеві графіки замість цього.

Аутедж затягнувся, бо симптом був неконсистентним. Лише деякі вузли падали. Лише під піком. А логи були шумними: скидання з’єднань, таймаути upstream, звичайний хаос. Ключовий рядок — accept4() failed (24: Too many open files) — був, але загубився в інших повідомленнях. Коли вони нарешті сфокусувалися, вони перевірили /proc/<pid>/limits для робітників проксі і знайшли крихітний RLIMIT у порівнянні зі shell.

Неправильне припущення було простим: «Якщо в моєму shell 200k, то і в сервісу теж». Це колись могло бути правдою в налаштуваннях, де демони запускалися скриптами в login-подібних середовищах. Під systemd — зазвичай хибно.

Виправлення було drop-in юніта: LimitNOFILE=131072, потім рестарт по хвилях. Постмортем інциденту не був про число FD; він був про дисципліну перевірки. Їхнє нове правило — практичне: жоден інцидент не закривається, доки хтось не вставить у тікет /proc/<pid>/limits.

Міні-історія 2: оптимізація, що дала зворотний ефект

Фінансова команда запускала message gateway з агресивним повторним використанням з’єднань. Хтось помітив, що підвищення FD «виправило» випадкові EMFILE і вирішив зробити ще більше. Вони підняли LimitNOFILE з десятків тисяч до майже максимального значення ядра і почали святкувати.

За кілька днів хост почав поводитися дивно під сплесками. Не EMFILE — тепер це був тиск на пам’ять і спорадичні стрибки латентності. Виявилося, що з новим простором gateway охоче тримав значно більше сокетів відкритими під час повільного upstream. Це збільшило використання пам’яті ядра для буферів сокетів і обліку. їхній реальний вузький горлечко був не в «замало FD», а в тому, що вони не скидали навантаження при повільному upstream.

Оптимізація дала зворотний ефект, бо прибрала запобіжник. Ліміт FD примушував випадковий зворотний тиск. Видаливши його без явного механізму зворотного тиску, вони отримали гірший режим відмови: хвостова латентність і каскадні ретраї.

Вони в кінці кінців погодилися на помірний FD-ліміт, плюс жорсткі межі для пулів з’єднань і таймаути. Урок не в «не піднімати ліміти». Урок — «піднімайте ліміти тільки після того, як зробили конкуренційну поведінку явною». Ліміти — це поруччя. Якщо ви їх знімаєте, встановіть інші поруччя спочатку.

Міні-історія 3: нудна, але правильна практика, що врятувала день

Велика внутрішня платформа мала суміш системних сервісів і сервісів користувача. Вони дотримувалися «параноїдальної» практики: будь-яка зміна продуктивності вимагала фрагмент перевірки у запиті на зміну. Для тюнінгу FD цей фрагмент завжди включав (1) властивості unit у systemd, (2) /proc limits для Main PID та одного воркера, і (3) підрахунок FD до/після під час навантажувального тесту.

Одного дня рутинний деплой ввів тонкий витік FD у клієнтській бібліотеці gRPC. Це не було катастрофою негайно; зайняло години. Їхній моніторинг вловив тренд: відкриті FD зростали лінійно. Оскільки їхній чекліст rollout включав «перевірку тренда FD», вони помітили це рано на канарці.

Вони відкотили до того, як витік досяг хард-капу. Ніякого аутеджу. Ніякого містка інцидентів. Просто трохи засмучений інженер і чистий графік.

Нічого героїчного не сталося. Ось у чому суть. Нудні практики рятують більше систем, ніж хитрі рішення. Перевірка перемагає відчуття.

Поширені помилки: симптом → корінна причина → виправлення

1) «Я підняв ulimit, але сервіс досі помилиться з Занадто багато відкритих файлів»

Симптом: ulimit -n у вашому shell виглядає високим; логи демона показують EMFILE.

Корінна причина: Демон запускається systemd з нижчим LimitNOFILE (або дефолтом).

Виправлення: Додайте systemd drop-in з LimitNOFILE=, зробіть reload, рестарт і перевірте через /proc/<pid>/limits.

2) «systemctl show каже LimitNOFILE високий, але /proc все ще показує низький»

Симптом: systemctl show -p LimitNOFILE виводить ваше нове значення; запущений PID не змінився і все ще обмежений.

Корінна причина: Ви не перезапустили сервіс, або дивитесь не на той процес (воркер vs мастер), або сервіс форкається/виконує exec в інший PID, який ви не перевірили.

Виправлення: Перезапустіть юніт, отримайте новий MainPID, інспектуйте PIDs у cgroup, перевірте /proc/<pid>/limits для процесу, що падає.

3) «Після підвищення лімітів хост нестабільний під навантаженням»

Симптом: Більше немає EMFILE, але латентність стрибає, пам’ять під тиском або зростає мережевий беклог.

Корінна причина: Вищі FD-ліміти дозволили більшій конкуренції без зворотного тиску. Буферизація сокетів і пам’ять ядра зростають.

Виправлення: Додайте явні контролі конкурентності (пули з’єднань, обмеження воркерів, таймаути). Встановіть FD-ліміти під дизайн, а не під его.

4) «Лише процеси, що запускаються користувачем, падають; системні сервіси в порядку»

Симптом: Інтерактивні інструменти (батч-джоби, дев-скрипти) отримують EMFILE; системні демони ні.

Корінна причина: PAM-ліміти та сесії користувача мають низькі дефолти; systemd system units налаштовані окремо.

Виправлення: Відрегулюйте PAM-ліміти (/etc/security/limits.conf або drop-ins у /etc/security/limits.d) та/або defaults менеджера користувача у /etc/systemd/user.conf. Перевірте в новій сесії.

5) «Ми встановили LimitNOFILE на 2 мільйони і systemd відмовився»

Симптом: Сервіс не стартує або ліміт обрізається до нижчого числа.

Корінна причина: Ядро fs.nr_open нижче. RLIMIT_NOFILE не може перевищувати його.

Виправлення: Підніміть fs.nr_open через sysctl (обережно), потім встановіть розумний ліміт. Також перевірте запас пам’яті.

6) «Падає лише після ротації логів»

Симптом: Після ротації FD зростає; зрештою з’являється EMFILE.

Корінна причина: Додаток тримає старі дескриптори логів відкритими (або допоміжний процес це робить). Не класичний «витік», але виглядає як один.

Виправлення: Забезпечте коректний reopen логів (наприклад HUP там, де потрібно), або використовуйте stdout/stderr у journald, де це можливо. Перевірте цільові FD у /proc/<pid>/fd.

Контрольні списки / покроковий план

Контрольний список A: чисто виправити один systemd-сервіс

  1. Підтвердьте EMFILE vs ENFILE в логах/journal.
  2. Отримайте MainPID юніта: systemctl show -p MainPID --value your.service.
  3. Прочитайте /proc/<pid>/limits, зафіксуйте поточний «Max open files».
  4. Створіть drop-in: systemctl edit your.service і встановіть LimitNOFILE=.
  5. systemctl daemon-reload, потім рестарт сервісу.
  6. Знову перевірте /proc/<pid>/limits на новому PID.
  7. Порахуйте FD і визначте, що це за дескриптори (/proc/<pid>/fd).
  8. Якщо FD зростає під стабільним навантаженням — вважайте це витоком, поки не доведете протилежне.

Контрольний список B: визначити правильне число (замість «зробити величезним»)

  1. Оцініть пікову конкурентність: з’єднання, файли, pipe, спостереження.
  2. Додайте запас, але тримайте безпечну межу на випадок runaway-поведінки (не виставляйте «нескінченність»).
  3. Переконайтесь, що системна стеля (fs.nr_open) вище за вашу ціль.
  4. Урахуйте вплив на пам’ять при великій кількості сокетів та буферів. FD не безкоштовні.
  5. Навантажте тест і спостерігайте: кількість FD, пам’ять, латентність, рівень помилок.

Контрольний список C: політика флоту (якщо треба чіпати дефолти)

  1. Документуйте, чому потрібна зміна дефолту і які сервіси виграють.
  2. Встановіть DefaultLimitNOFILE обережно. Використовуйте per-service overrides для винятків.
  3. Розгорніть поступово і моніторте нові режими відмов (пам’ять, латентність).
  4. Стандартизруйте перевірку: властивість юніта + /proc ліміти + тренд кількості FD.

Цікаві факти та історичний контекст

  • Факт 1: Початкова ідея Unix «все — файл» зробила файлові дескриптори універсальними ручками — чудово для композиційності, іноді причина відмов.
  • Факт 2: Ранні Unix системи мали малі дефолтні ліміти FD (часто 64 або 256). Багато дефолтів залишалися консервативними десятиліттями, щоб захистити спільні машини.
  • Факт 3: RLIMIT_NOFILE має «soft» і «hard» значення; soft-ліміт найчастіше тригерить EMFILE.
  • Факт 4: systemd не просто замінив init-скрипти; він стандартизував, як сервіси успадковують ліміти і членство в cgroup, роблячи поведінку більш передбачуваною — коли ви вивчите, де дивитися.
  • Факт 5: /proc/<pid>/limits — одне з найпростішіх джерел істини в Linux. Його кримінально мало використовують в інцидент-респонсі.
  • Факт 6: «Занадто багато відкритих файлів» часто стосується сокетів, а не файлів. Нетривіальні мережеві демони можуть вдарити по цьому без торкання диска.
  • Факт 7: Підвищення FD-лімітів може суттєво збільшити використання пам’яті ядра, бо сокети та epoll/notify-структури мають витрати на кожен об’єкт.
  • Факт 8: Деякі сервіси мають внутрішні обмеження (ліміти воркерів, пули з’єднань), які треба узгоджувати з OS-лімітами інакше ви отримаєте петлю «підняли ОС, все ще зламалося».

FAQ

1) Чому ulimit -n не виправляє мій systemd сервіс?

Тому що ваш systemd сервіс не є дочірнім процесом вашого інтерактивного shell. systemd встановлює rlimits при запуску сервісу на основі юніта та defaults systemd. Ваш shell ulimit впливає лише на процеси, створені з цього shell.

2) Чи ігнорується /etc/security/limits.conf в Debian 13?

Ні. Він застосовується там, де застосовується PAM: сесії входу (ssh, консоль, деякі шляхи su/sudo залежно від конфігурації). Багато системних сервісів не проходять через PAM, тому вони не успадковують ці ліміти.

3) Чи варто ставити DefaultLimitNOFILE глобально?

Зазвичай ні. Встановлюйте per-service LimitNOFILE через drop-ins. Глобальні дефолти доречні лише коли ви впроваджуєте базовий рівень для флоту і розумієте побічні ефекти.

4) Яке число обрати для LimitNOFILE?

Обирайте на основі виміряного пікового використання FD плюс запас, а не на вірі. Для завантажених проксі десятки — сотні тисяч можуть бути нормою. Для багатьох додатків 8192–65536 вистачає. Навантажте тест і дивіться /proc/<pid>/fd.

5) Чи можна підняти LimitNOFILE без перезапуску сервісу?

Ні. RLIMIT_NOFILE задається на процес. Треба перезапустити (або зробити re-exec) процес, щоб застосувати новий ліміт. systemd не може заднім числом змінити rlimit у вже запущеного процесу.

6) Я підняв LimitNOFILE, але помилки залишилися. Що далі?

Перевірте, чи ви змінили саме процес, що падає (перевірте воркерів). Потім інспектуйте використання FD та їх типи. Якщо FD ростуть постійно — швидше за все витік. Якщо вони стрибкоподібно зростають з трафіком — можливо потрібен кращий контроль конкурентності або налаштування внутрішніх лімітів додатку.

7) Чим знову EMFILE відрізняється від ENFILE?

EMFILE — це пер-процесна помилка: процес досяг RLIMIT_NOFILE. ENFILE — системна: таблиця файлів хоста вичерпана. Більшість інцидентів «Too many open files» у сервісах — це EMFILE.

8) Чи змінюють контейнери цю історію?

Так, але принцип залишається: процес має реальний RLIMIT. Рантайми контейнерів можуть його встановлювати; systemd всередині контейнера може встановлювати його; хост може обмежувати. Все одно перевіряйте /proc/<pid>/limits у відповідному неймспейсі/середовищі.

9) Чи може високий FD-ліміт бути ризиком безпеки?

Він може бути ризиком доступності. Якщо скомпрометований або багований сервіс може відкрити мільйони FD, він може вичерпати ресурси ядра і деградувати хост. Ліміти — частина контролю площі ураження.

Висновок: наступні кроки, які можна зробити сьогодні

Виправлення «Занадто багато відкритих файлів» на Debian 13 — це не голосніше введений ulimit. Це про встановлення ліміту там, де сервіс народжується: systemd. Використайте per-service drop-in з LimitNOFILE. Зробіть reload. Перезапустіть. Перевірте в /proc/<pid>/limits. Потім виміряйте використання FD, щоб знати, чи ви вирішили проблему ємності або лише відтермінували витік.

Практичні наступні кроки:

  1. Виберіть один сервіс, що коли-небудь викидав EMFILE, і додайте drop-in з розумним LimitNOFILE.
  2. Зробіть маленький runbook: MainPID → /proc limits → FD count.
  3. Під час наступного навантажувального тесту зафіксуйте пікове FD-використання і встановіть ліміти на основі даних, а не фольклору.

Якщо ви зробите це один раз, ви перестанете ставитися до файлових дескрипторів як до таємничих гріммів і почнете сприймати їх як те, чим вони є: бюджетом. І бюджети, на жаль, завжди мають значення.

← Попередня
ZFS і Kubernetes: дизайн PV, який не підведе під час відмови вузлів
Наступна →
Електронна пошта: HELO/EHLO неправильно — виправте це, поки провайдери не обмежили вас

Залишити коментар