Починається з кількох 502-ок. Потім ваші графіки виглядають як сейсмограф. Nginx «працює», навантаження системи в нормі, але клієнти отримують скидання з’єднань, а лог помилок починає повторювати: Too many open files.
На Ubuntu 24.04 вирішення рідко полягає в простому «підняти ulimit». Така порада дає гарне число в шеллі й той самий інцидент у проді. Правильне рішення — це про ліміти unit-ів systemd, власні обмеження Nginx і глобальний бюджет дескрипторів ядра — плюс перевірка всього цими командами, які не брешуть.
Що насправді означає «Too many open files» для Nginx
Ядро повертає EMFILE, коли процес досягнув свого обмеження дескрипторів на процес. Nginx записує це як «Too many open files», коли не може відкрити сокет, прийняти з’єднання, відкрити файл або встановити upstream-з’єднання.
Важлива деталь: «файли» в Nginx — це не лише файли. Це сокети, пайпи, eventfd, signalfd та інші ресурси, які відкриває процес. Якщо Nginx — завантажений зворотний проксі, більшість дескрипторів — це сокети: клієнтські з’єднання + upstream-з’єднання + прослуховуючі сокети + файли логів. Якщо він віддає статичні файли, дескриптори також будуть відкритими файловими дескрипторами. Використання open_file_cache може навмисно збільшити кількість відкритих файлових дескрипторів. Якщо ви працюєте з HTTP/2 або HTTP/3, шаблони з’єднань змінюються, але облік дескрипторів усе одно важливий.
На Ubuntu 24.04 Nginx зазвичай керується systemd. Це означає:
/etc/security/limits.confненадійно застосовується до сервісів systemd.- Те, що ви встановлюєте в інтерактивному шеллі (
ulimit -n), не має значення для master-процесу Nginx, запущеного systemd. - Існує декілька шарів лімітів: конфіг Nginx, unit systemd, PAM-ліміти для сесій користувачів і максимум ядра.
Ось єдина ментальна модель, яка послідовно запобігає циклу «я підняв — чому все ще зламано?»:
- Глобальний потолок ядра: скільки дескрипторів може виділити вся система (
fs.file-max,file-nr), плюс максимальний пер-процес значення, яке можна встановити (fs.nr_open). - Потолок сервісу systemd:
LimitNOFILEв unit-і Nginx (або дефолт systemd, що застосований до всіх сервісів). - Внутрішній потолок Nginx:
worker_rlimit_nofileі максимум, який випливає зworker_connections. - Реальність: фактична кількість відкритих дескрипторів під навантаженням.
Також «Too many open files» може бути симптомом витоку. Це не завжди «треба більше». Якщо дескриптори зростають постійно при стабільному трафіку, щось не закривається. Це може бути upstream, модуль, що некоректно працює, або шаблон логування/кешування, який не звільняє ресурси.
Жарт #1: Файлові дескриптори — як чашки для кави на кухні офісу: якщо ніхто їх не повертає, рано чи пізно нікому не налити, і хтось звинуватить посудомийну машину.
Швидкий план діагностики (що перевірити спочатку)
Це послідовність, яка швидко знаходить вузьке місце, без суперечок із відчуттями.
1) Підтвердіть помилку і де вона трапляється
- Лог помилок Nginx: чи відмовляє він в
accept(),open()або upstream-сокетах? - Журнал systemd: чи бачите ви «accept4() failed (24: Too many open files)» або подібне?
2) Перевірте ліміти процесу Nginx так, як їх бачить ядро
- Прочитайте
/proc/<pid>/limitsдля master-а та воркера Nginx. - Якщо ліміт низький (1024/4096), майже завжди це конфігурація systemd, а не Nginx.
3) Виміряйте реальне використання FD і його зростання
- Порахуйте відкриті дескриптори на воркер.
- Шукайте монотонне зростання при стабільному навантаженні (запах витоку).
4) Перевірте системну ємність
sysctl fs.file-maxіcat /proc/sys/fs/nr_opencat /proc/sys/fs/file-nrщоб побачити виділені проти використаних.
5) Лише потім налаштовуйте
- Піднімайте
LimitNOFILEв systemd drop-in. - Встановіть
worker_rlimit_nofileі узгодьтеworker_connections. - Перевірте, не припускайтесь помилок, і прогрейте тестування навантаженням (або хоча б перевірте на реальному трафіку).
Цікаві факти і контекст (чому це повторюється)
- Факт 1: Unix файлові дескриптори старіші за сучасні мережі; сокети навмисно зроблені схожими на файли, щоб повторно використовувати API.
- Факт 2: Класичний дефолт soft limit 1024 походить із епохи, коли 1000 одночасних з’єднань звучало як наукова фантастика, а не як звичайний вівторок.
- Факт 3: Подієво-орієнтована модель Nginx ефективна частково тому, що вона тримає багато відкритих з’єднань одночасно — отже, якщо дозволити, вона легко споживає FD.
- Факт 4: systemd не успадковує автоматично ваш shell-
ulimit; сервіси отримують ліміти з unit-файлів і дефолтів systemd. - Факт 5: На Linux
fs.nr_openобмежує максимальне значення per-process open files, які ви можете встановити, навіть як root. - Факт 6: «Too many open files» може з’явитися навіть коли загальна системна кількість дескрипторів у нормі — бо пер-процесні ліміти кусають першими.
- Факт 7: Nginx може досягати лімітів FD швидше при проксінгу, бо кожне клієнтське з’єднання може вимагати upstream-з’єднання (іноді більше одного).
- Факт 8: Виснаження ephemeral-портів часто неправильно діагностують як виснаження FD; обидва виглядають як помилки з’єднань, але це різні налаштування.
Одна парафразована ідея від відомого SRE-голосу: Надійність походить із навчання, а не з звинувачень — інструментуйте систему і перевіряйте припущення.
Практичні завдання: команди, очікуваний вивід і рішення (12+)
Це «зроби це зараз» завдання. Кожне містить, що означає вивід і яке рішення прийняти далі. Виконуйте від root або з sudo де потрібно.
Завдання 1: Підтвердьте помилку в логах Nginx
cr0x@server:~$ sudo tail -n 50 /var/log/nginx/error.log
2025/12/30 02:14:01 [crit] 2143#2143: *98123 accept4() failed (24: Too many open files)
2025/12/30 02:14:01 [alert] 2143#2143: open() "/var/log/nginx/access.log" failed (24: Too many open files)
Значення: Nginx досяг пер-процесного потолку FD. Помилка в accept4() означає, що він не може прийняти нові клієнтські з’єднання. Неможливість відкрити логи — теж тривожний сигнал: система вже в проблемі.
Рішення: Негайно перевірте ліміти процесу Nginx і поточне використання FD перед тим, як щось змінювати.
Завдання 2: Перевірте журнал systemd на пов’язані повідомлення
cr0x@server:~$ sudo journalctl -u nginx --since "30 minutes ago" | tail -n 30
Dec 30 02:14:01 server nginx[2143]: 2025/12/30 02:14:01 [crit] 2143#2143: *98123 accept4() failed (24: Too many open files)
Dec 30 02:14:02 server systemd[1]: nginx.service: Reloading.
Dec 30 02:14:03 server nginx[2143]: 2025/12/30 02:14:03 [alert] 2143#2143: open() "/var/log/nginx/access.log" failed (24: Too many open files)
Значення: Підтверджує, що це Nginx, а не клієнтська чутка. Також показує, чи відбувалися рестарти/перезавантаження під час події.
Рішення: Якщо перезавантаження відбуваються автоматично (конфігураційні цикли), ви можете посилювати проблему. Розгляньте паузу автоматизації до виправлення лімітів.
Завдання 3: Знайдіть PID master-а Nginx
cr0x@server:~$ pidof nginx
2143 2144 2145 2146
Значення: Зазвичай перший PID — master, решта — воркери (залежить від збірки/опцій).
Рішення: Визначте, який PID master, а які воркери; вам потрібно інспектувати обидва.
Завдання 4: Підтвердіть ролі master/worker
cr0x@server:~$ ps -o pid,ppid,user,cmd -C nginx
PID PPID USER CMD
2143 1 root nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
2144 2143 www-data nginx: worker process
2145 2143 www-data nginx: worker process
2146 2143 www-data nginx: worker process
Значення: Master працює під root, воркери — під www-data. Ліміти можуть відрізнятися, якщо щось дивне відбувається, але зазвичай вони успадковуються від master-а.
Рішення: Перевірте ліміти як на master, так і на воркерах — не робіть припущень про спадкування.
Завдання 5: Перевірте поточні FD-ліміти через /proc
cr0x@server:~$ sudo cat /proc/2143/limits | grep -i "open files"
Max open files 1024 1024 files
Значення: Це — димова гармата: 1024 — несерйозний продакшен-ліміт для Nginx як зворотного проксі.
Рішення: Виправте LimitNOFILE в systemd. Зміни ulimit в шеллі не допоможуть.
Завдання 6: Порахуйте відкриті дескриптори для воркера
cr0x@server:~$ sudo ls -1 /proc/2144/fd | wc -l
1007
Значення: Воркер майже досягає ліміту. Ви не уявляєте собі це — так і є.
Рішення: Якщо використання близьке до ліміту — підніміть його. Якщо далеко — можливо ви гонитесь за неправильним симптомом (рідко, але перевірте).
Завдання 7: Визначте типи відкритих FD (сокети проти файлів)
cr0x@server:~$ sudo lsof -p 2144 | awk '{print $5}' | sort | uniq -c | sort -nr | head
812 IPv4
171 IPv6
12 REG
5 FIFO
Значення: Переважно сокети (IPv4/IPv6). Це конкуруюча одночасність з’єднань, а не накопичення файлових дескрипторів через статичні файли.
Рішення: Зосередьтеся на лімітах на з’єднання на воркер, поведінці keepalive і повторному використанні upstream-з’єднань — не лише на «менше файлів віддавати».
Завдання 8: Перевірте конфіг Nginx щодо лімітів воркерів
cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n 'worker_(processes|connections|rlimit_nofile)' | head -n 30
12:worker_processes auto;
18:worker_connections 768;
Значення: worker_rlimit_nofile не встановлено, а worker_connections — скромний. З keepalive і проксінгом 768 може давати тиск, але більшою проблемою є ОС-ліміт 1024.
Рішення: Узгодьте LimitNOFILE і worker_rlimit_nofile з реалістичною метою. Потім перегляньте worker_connections відповідно до очікуваної одночасності.
Завдання 9: Перевірте поточні systemd-ліміти для сервісу nginx
cr0x@server:~$ sudo systemctl show nginx -p LimitNOFILE
LimitNOFILE=1024
Значення: systemd встановлює 1024 для сервісу. Це перекреслює ваші сподівання та мрії.
Рішення: Додайте systemd drop-in override для nginx з більшим LimitNOFILE.
Завдання 10: Перевірте глобальні статистики файлових дескрипторів ядра
cr0x@server:~$ cat /proc/sys/fs/file-max
9223372036854775807
cr0x@server:~$ cat /proc/sys/fs/file-nr
3648 0 9223372036854775807
Значення: На сучасному Ubuntu file-max може бути фактично «дуже великий». file-nr показує виділені дескриптори (~3648). Система далека від глобального виснаження FD.
Рішення: Поки не чіпайте глобальний fs.file-max. Ваш вузький горлечко — пер-процесні ліміти/ліміти сервісу.
Завдання 11: Перевірте fs.nr_open (пер-процесний жорсткий кап)
cr0x@server:~$ cat /proc/sys/fs/nr_open
1048576
Значення: Можна встановити пер-процесні ліміти до 1,048,576. Достатньо запасу.
Рішення: Оберіть розумне число (часто 65535 або 131072), а не «безкінечність».
Завдання 12: Перевірте, скільки з’єднань Nginx реально обробляє
cr0x@server:~$ sudo ss -s
Total: 2389 (kernel 0)
TCP: 1920 (estab 1440, closed 312, orphaned 0, timewait 311)
Значення: Якщо встановлених з’єднань багато, використання FD буде високим. TIME_WAIT не є FD в тому ж сенсі, але вказує на поведінку трафіку і keepalive.
Рішення: Якщо встановлені з’єднання корелюють з помилкою — підняття FD-лімітів обґрунтоване. Якщо встановлених мало, а FD багато — підозрюйте витік або кеші.
Завдання 13: Перевірте активний шлях unit-файлу Nginx і drop-in
cr0x@server:~$ sudo systemctl status nginx | sed -n '1,12p'
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Drop-In: /etc/systemd/system/nginx.service.d
└─ override.conf
Active: active (running) since Tue 2025-12-30 01:58:12 UTC; 16min ago
Значення: Показує, звідки завантажено unit і чи існують drop-in. Якщо ви не бачите директорії drop-in, ви ще нічого не перевизначали.
Рішення: Віддавайте перевагу drop-in override під /etc/systemd/system/nginx.service.d/. Не редагуйте vendor-юнити в /usr/lib.
Завдання 14: Після змін підтвердіть, що новий ліміт застосовано
cr0x@server:~$ sudo systemctl show nginx -p LimitNOFILE
LimitNOFILE=65535
cr0x@server:~$ sudo cat /proc/$(pidof nginx | awk '{print $1}')/limits | grep -i "open files"
Max open files 65535 65535 files
Значення: systemd тепер дає 65535 і запущений master-процес має це. Це крок перевірки, який люди пропускають, а потім дивуються.
Рішення: Якщо значення не співпадають — ви неправильно перезапустили сервіс, override не завантажився або інший setting перемагає.
Виправити правильно: LimitNOFILE systemd для Nginx
Ubuntu 24.04 працює на systemd. Nginx ймовірно запускається як nginx.service. Правильний підхід — override для unit-а (drop-in), не ручне редагування unit-файлів і не покладання на limits.conf.
Виберіть розумне число
Поширені значення для продакшену:
- 65535: класичне, широко використовуване, зазвичай достатньо для однонодового Nginx при звичайному проксінгу.
- 131072: для дуже високої одночасності або багатьох upstream-навантажень.
- 262144+: рідко; виправдане тільки після вимірювань і наявної причини (і зазвичай з’являються інші вузькі місця першими).
Не ставте «мільйон», лише тому що можете. Великий FD-ліміт може довше приховувати витік і збільшує площу ураження, якщо щось піде дуже погано.
Створіть systemd drop-in override
cr0x@server:~$ sudo systemctl edit nginx
# (an editor opens)
Додайте це:
cr0x@server:~$ cat /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65535
Рішення: Якщо ви також запускаєте Nginx у контейнері або через інший unit (наприклад, кастомний обгорт), застосуйте override до правильного імені unit-а, а не того, яке вам би хотілося використовувати.
Перезавантажте systemd і рестартуйте Nginx
cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart nginx
cr0x@server:~$ sudo systemctl is-active nginx
active
Значення: daemon-reload змушує systemd перечитати unit-и. Для застосування нових лімітів потрібен рестарт; reload не завжди знову застосовує ресурси до вже працюючого процесу.
Перевіряйте, а не припускайте
cr0x@server:~$ sudo systemctl show nginx -p LimitNOFILE
LimitNOFILE=65535
Якщо це показує правильне значення, але /proc/<pid>/limits — ні, ви, ймовірно, дивитесь на старий PID (сервіс не перезапустився) або Nginx запускається іншим способом.
А як щодо DefaultLimitNOFILE?
systemd може встановлювати дефолтні ліміти для всіх сервісів через /etc/systemd/system.conf (і user.conf для user-сервісів). Це заманливо в корпоративному середовищі, бо «фіксує все». Але це також змінює все.
Моя думка: для Nginx використовуйте per-service override, якщо у вас немає зрілого базового підходу та розуміння впливу на всі демон-рядки. Встановлення великого дефолту може ненавмисно дозволити іншим процесам відкривати величезну кількість файлів, що не завжди добре.
Виправлення на боці Nginx: worker_rlimit_nofile, worker_connections, keepalive
systemd може надати Nginx 65k FD, але Nginx все одно має використовувати їх розумно.
worker_rlimit_nofile: узгодьте Nginx з ОС-лімітом
Додайте у верхній (топ-левел) контекст /etc/nginx/nginx.conf:
cr0x@server:~$ sudo grep -n 'worker_rlimit_nofile' /etc/nginx/nginx.conf || true
cr0x@server:~$ sudoedit /etc/nginx/nginx.conf
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Приклад фрагмента, який можна додати:
cr0x@server:~$ sudo awk 'NR==1,NR==30{print}' /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 8192;
}
Значення: worker_rlimit_nofile встановлює RLIMIT_NOFILE для воркер-процесів (а іноді й для master-а, залежно від збірки/поведінки). Якщо не встановити, Nginx може працювати з системним лімітом, але явне узгодження уникне сюрпризів.
Рішення: Встановіть worker_rlimit_nofile на те саме (або трохи менше) значення, ніж LimitNOFILE systemd. Якщо встановите вище, Nginx не перевищить ОС-ліміт.
worker_connections: це не «скільки користувачів», це «скільки сокетів»
У блоці events worker_connections визначає максимальну кількість одночасних з’єднань на воркер-процес. Орієнтовно:
- Макс клієнтських з’єднань ≈
worker_processes * worker_connections - Але при зворотньому проксінгу часто множник сокетів ≈ 2: клієнтський сокет + upstream-сокет.
- Плюс накладні витрати: прослуховуючі сокети, логи, внутрішні пайпи, резолвер-сокети тощо.
Якщо ви встановили worker_connections у 50k, а FD-ліміт на 65k, ви проводите математичні обчислення із завзятістю малюка та точністю димової машини.
Keepalive і проксінг: хитрий множник FD
Keepalive — чудово, поки ні. З keepalive клієнти і upstream тримають сокети відкритими довше. Це покращує латентність і знижує витрати на хендшейки, але підвищує стійке використання FD. При різкому трафіку це може перетворити «була хвиля» на «30 секунд постійного тиску на дескриптори».
Місця для перевірки:
keepalive_timeoutіkeepalive_requests(клієнтська сторона)proxy_http_version 1.1іproxy_set_header Connection ""(коректність upstream-keepalive)keepaliveв upstream-блоках (пули з’єднань)
Жарт #2: Keepalive — як резервувати кімнату для зустрічей «на всякий випадок» — врешті-решт ніхто не може працювати, але календар виглядає чудово.
Перезавантажуйте безпечно
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl reload nginx
cr0x@server:~$ sudo systemctl status nginx | sed -n '1,10p'
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Drop-In: /etc/systemd/system/nginx.service.d
└─ override.conf
Active: active (running) since Tue 2025-12-30 02:20:11 UTC; 2min ago
Значення: Reload застосовує зміни конфігурації без втрати з’єднань (переважно), але зміни FD-лімітів вимагали рестарту раніше. Тепер ви ітеруєтеся над налаштуваннями Nginx.
Ліміти ядра і системні: fs.file-max, fs.nr_open, ephemeral ports
Більшість інцидентів «Too many open files» в Nginx — це пер-процесні ліміти. Але варто розуміти системні важелі, бо це наступний режим відмови при масштабуванні.
Системна ємність файлових дескрипторів
Ключові індикатори:
/proc/sys/fs/file-max: глобальний максимум відкритих файлів (на сучасних системах може бути дуже великим)./proc/sys/fs/file-nr: виділені, невикористані, максимальні. Швидке зростання виділених може сигналити про сплески або витоки по всій системі.fs.nr_open: максимальне значення для пер-процесних лімітів.
Якщо глобальна кількість близька до максимуму, підняття пер-процесного ліміту Nginx не допоможе. Ви просто перемістите відмову з Nginx на інші сервіси, і ядро почне відмовляти у виділенні інакше.
Ephemeral ports: інше «занадто багато»
Коли Nginx — зворотний проксі, кожне upstream-з’єднання споживає локальний ephemeral порт. Якщо ви сильно навантажуєте невеликий пул upstream короткоживучими з’єднаннями, можете виснажити ephemeral-порти або застрягти в TIME_WAIT. Симптоми можуть нагадувати тиск на FD: збої з’єднань, таймаути upstream, збільшення 499/502/504.
Швидкі перевірки:
cr0x@server:~$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 60999
cr0x@server:~$ ss -tan state time-wait | wc -l
311
Значення: Дефолтний діапазон ephemeral — близько 28k портів. Кількість TIME_WAIT показує, скільки нещодавно закритих з’єднань ще перебувають у стані. Не всі TIME_WAIT шкідливі, але велика кількість з високим оборотом може заважати.
Рішення: Якщо портовий тиск реальний, його вирішують повторним використанням з’єднань (upstream keepalive), розподілом навантаження і буваємо — налаштуванням діапазону портів, а не лише підняттям FD-лімітів.
Чи потрібно чіпати /etc/security/limits.conf?
Для сервісів systemd: зазвичай ні. Цей файл застосовується через PAM до сесій користувачів (SSH, логіни тощо). Nginx, запущений systemd, його не читає. Ви все ще можете встановити там значення для послідовності, але не очікуйте, що це вирішить простій.
Перевірка у інтерактивному шеллі:
cr0x@server:~$ ulimit -n
1024
Значення: Це — ваш shell-ліміт, не Nginx-ліміт. Корисно лише щоб підтвердити, що ви самі можете робити, а не що сервіс робить.
Рішення: Не «виправляйте» продакшен, просто вставивши ulimit -n 65535 в шелл і відчуваючи себе задоволеним.
Планування ємності: скільки FD вам реально треба?
Планування ємності для файлових дескрипторів — нудне заняття. Саме тому воно працює.
Емпірична модель рахунку
Почніть з цієї приблизної моделі:
- На активне клієнтське з’єднання: ~1 FD (клієнтський сокет)
- На проксований запит: часто +1 FD (upstream-сокет), іноді більше при ретраях/декількох upstream
- На воркер-базу: кілька десятків FD (логи, eventfd, пайпи, спільні прослуховуючі сокети тощо)
Якщо ви обслуговуєте лише статичний контент і використовуєте sendfile, ви все одно відкриваєте файлові дескриптори, але вони можуть бути короткоживучими. Якщо ввімкнули file cache, вони можуть бути довгоживучими.
Обчисліть практичну ціль
Приклад: очікуєте до 20,000 одночасних клієнтських з’єднань на вузлі і проксуєте з keepalive. Консервативно припустіть 2 FD на клієнта в піку (клієнт + upstream): 40,000. Додайте накладні витрати: скажімо 2,000. Ділити по воркерах? Не зовсім — з’єднання розподіляються, але нерівномірність трапляється. Якщо у вас 8 воркерів, в гіршому випадку один воркер може тимчасово держати більше, ніж середнє, залежно від поведінки accept і планування.
Ось чому дають запас. LimitNOFILE у 65535 на процес — популярна золота середина: достатньо велике, щоб уникнути дурних відмов, але достатньо обмежене, щоб витоки проявлялись до катастрофи.
Вимірюйте, не гадіть
Після встановлення лімітів продовжуйте вимірювати. Для живого воркеру:
cr0x@server:~$ sudo bash -c 'for p in $(pgrep -u www-data nginx); do echo -n "$p "; ls /proc/$p/fd | wc -l; done'
2144 1842
2145 1760
2146 1791
Значення: Кількість FD на воркер під навантаженням. Якщо ці числа тримаються близько до ліміту — потрібні або вищі ліміти, або менше довгоживучих сокетів (тюнінг keepalive/upstream) або більше потужності (додаткові інстанси).
Рішення: Якщо лічильники стабільні і з комфортним запасом від ліміту — припиніть тюнити. Виправте і рухайтесь далі. Інженерія — не спорт, де ви виграєте, змінюючи більше перемикачів.
Три міні-історії з корпоративних фронтів
Міні-історія 1: Інцидент через неправильне припущення
Команда мігрувала з старішої версії Ubuntu на 24.04 як частину оновлення безпеки. Конфіг Nginx не змінили. Профіль трафіку не змінився. Але протягом кількох днів вони отримали раптові відмови під час прогнозованих піків.
На-call інженер зробив те, що кожен Linux-людина робив принаймні раз: зайшов через SSH, виконав ulimit -n 65535, перезапустив Nginx вручну, побачив, що проблема зникла, і пішов спати. Наступного дня все повторилося. Так воно стало ритуалом.
Неправильне припущення було тонким: вони вірили, що їх shell-ліміт застосовується до сервісу після рестарту. Не застосовувався. Nginx стартував systemd під час нерегульованих рестартів і хост-обслуговування, завжди повертуючись до LimitNOFILE=1024. Ручне виправлення діяло лише коли Nginx запускали з шеллу з піднятим лімітом.
Міцне виправлення зайняло десять хвилин: systemd drop-in з LimitNOFILE, потім перевірка через /proc/<pid>/limits. Реальний виграш, проте, був соціальний: вони задокументували крок перевірки в інцидентному шаблоні. Майбутні on-call перестали ставити «ulimit» як магічний обряд.
Міні-історія 2: Оптимізація, що відбилася назад
Інженер, орієнтований на продуктивність, захотів зменшити латентність upstream. Вони ввімкнули агресивні keepalive на клієнтській і upstream-сторонах. Зміна виглядала блискуче в синтетичному тесті: менше хендшейків, нижчий p95, кращі дашборди.
Потім продакшен зробив своє. Сервіс мав багато клієнтів з довгим хвостом (мобільні мережі, корпоративні проксі), які тримали з’єднання відкритими надовго. З підвищеним keepalive Nginx утримував значно більше одночасних сокетів. Дескриптори залишалися виділеними довше і система повільно підходила до пер-процесного ліміту в пікові години.
Гірше: команда також ввімкнула більший open_file_cache для статичних активів на тих самих вузлах, додаючи ще довгоживучих файлових дескрипторів. Сумарний ефект викликав помилку в несподіваних місцях — іноді під час деплою (при ротації логів), іноді під час сплесків трафіку.
Виправлення не було простим «відключіть keepalive», бо це повернуло б витрати на латентність. Виправлення — дорослий підхід: підняти LimitNOFILE правильно, обмежити worker_connections до виправданого числа і налаштувати keepalive timeouts під реальну поведінку клієнтів. Вони також розділили ролі: вузли з великим об’ємом статичного контенту отримали інші налаштування кешування, ніж вузли-проксі. Оптимізації можуть працювати; вони просто потребують бюджету.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Інша організація мала простий рукбук: щоразу при зміні налаштувань паралельності Nginx вони записували три числа в задачу зміни: LimitNOFILE, worker_rlimit_nofile і пік спостережуваних /proc/<pid>/fd. Не гламурно. Не «інноваційно». Ефективно.
Одного вечора upstream-залежність почала час від часу тайм-аутитись. Клієнтські з’єднання накопичувалися, поки Nginx чекав відповіді upstream. Конкурентність зросла. Це нормально при уповільненні upstream: черги формуються на шарі проксі.
Але проксі не впало. Чому? Бо їх FD-ліміти були встановлені з запасом, і у них було моніторування, яке сповіщало при 70% від ліміту FD на воркер. On-call побачив алерт, упізнав його як утримання сокетів через upstream і ескалував до команди залежності, одночасно лімітував шумний endpoint. Ніякого каскадного відмови. Ніякого «Nginx помер».
Пост-інцидентний звіт був майже нудним, що є найвищою похвалою для операційної практики.
Поширені помилки: симптом → корінь → виправлення
1) Симптом: Ви встановили ulimit в 65535, але Nginx все ще пише EMFILE
Корінь: Nginx керується systemd; shell-ліміти не застосовуються. systemd все ще має LimitNOFILE=1024.
Виправлення: Додайте systemd drop-in override для nginx.service з LimitNOFILE=65535, перезапустіть, перевірте через /proc/<pid>/limits.
2) Симптом: systemctl show показує великий LimitNOFILE, але /proc показує низький
Корінь: Ви змінили unit, але не перезапустили сервіс; або дивитесь не на той PID; або Nginx запускається іншим unit-ом/обгорткою.
Виправлення: systemctl daemon-reload, потім systemctl restart nginx. Перевірте master PID і прочитайте ліміти з цього PID.
3) Симптом: Підняття LimitNOFILE допомогло на день, потім помилки повернулись
Корінь: Витік або поступове зростання конкурентності через повільність upstream або поведінку клієнтів. Ви лікували симптом, а не тенденцію.
Виправлення: Відстежуйте використання FD з часом на воркер. Корелюйте з латентністю upstream і активними з’єднаннями. Виправіть keepalive, таймаути, pooling upstream або сам upstream.
4) Симптом: Помилки лише під час ротації логів або перезавантажень
Корінь: Коли ви близькі до ліміту FD, банальні дії як повторне відкриття логів вимагають вільних дескрипторів. У вас нуль запасу.
Виправлення: Підніміть ліміти і переконайтеся, що нормальна кількість FD залишається нижче ~70–80% від ліміту в піку.
5) Симптом: Nginx приймає з’єднання, але проксінг upstream не вдається
Корінь: Можливо, у вас достатньо FD для клієнтів, але не для upstream-сокетів, або ж виснажені ephemeral-порти.
Виправлення: Збільшіть FD-ліміт і впевніться, що upstream keepalive працює. Перевірте діапазон ephemeral-портів і TIME_WAIT; виправте повторне використання з’єднань.
6) Симптом: Лише один воркер досягає ліміту і «плавиться»
Корінь: Нерівномірний розподіл з’єднань, можливо через accept mutex, CPU pinning або патерни трафіку.
Виправлення: Переконайтеся, що worker_processes auto відповідає CPU, перегляньте налаштування accept, і перевірте розподіл навантаження. Часто реальне рішення — трохи більше запасу плюс впевнення, що upstream не змушує довго тримати сокети.
7) Симптом: Ви значно підняли worker_connections і стало ще гірше
Корінь: Ви збільшили теоретичну конкурентність, не переконавшись, що пам’ять, upstream-можливості і FD-ліміти відповідають. Ви запросили велику тиску.
Виправлення: Підійміть worker_connections відповідно до того, що система може витримати. Використовуйте rate limiting, черги або масштабування замість нескінченної конкурентності.
Чеклісти / поетапний план
Покроково: безпечний для продакшену шлях виправлення
- Зафіксуйте докази: tail лог помилок Nginx і журнал на
EMFILE. - Знайдіть PIDs: ідентифікуйте master і воркери.
- Прочитайте живі ліміти:
/proc/<pid>/limitsдля master-а і воркера. - Виміряйте використання: порахуйте
/proc/<pid>/fdдля воркерів; зробіть кілька вимірів під навантаженням. - Перевірте systemd-ліміт:
systemctl show nginx -p LimitNOFILE. - Реалізуйте systemd override:
systemctl edit nginx→LimitNOFILE=65535. - Рестарт (не reload): застосуйте ліміти рестартом.
- Знову перевірте: systemd показує новий ліміт, /proc показує новий ліміт.
- Узгодьте конфіг Nginx: встановіть
worker_rlimit_nofile, перегляньтеworker_connectionsвідповідно до виміряної конкурентності. - Перезавантажте конфіг Nginx:
nginx -tпотімsystemctl reload nginx. - Спостерігайте за регресіями: моніторте відкриті FD на воркер; сповіщення при 70–80% від ліміту.
- Досліджуйте корені: якщо використання FD росте по часу, шукайте витоки або повільні upstream — не просто піднімайте числа знов.
Чекліст: як виглядає «добре» після виправлення
systemctl show nginx -p LimitNOFILEповертає цільове значення./proc/<masterpid>/limitsзбігається з цим значенням.- Worker
/proc/<pid>/fdмає запас при піку. - У логах Nginx більше не з’являється
accept4() failed (24: Too many open files). - Деплої/reload-и і ротація логів не викликають відмов з’єднань.
Чекліст: коли підняття лімітів — неправильне рішення
- Кількість FD зростає монотонно годинами при стабільному трафіку (шаблон витоку).
- Upstream повільний і з’єднання накопичуються; потрібні timeouts, backpressure або масштабування.
- Збої з’єднань корелюють з великою кількістю TIME_WAIT і вузьким портовим діапазоном (виснаження портів).
- Проблеми з пам’яттю вже є; збільшення конкурентності погіршить латентність і відмови.
FAQ (реальні питання о 02:00)
1) Чому /etc/security/limits.conf не виправляє Nginx на Ubuntu 24.04?
Тому що Nginx працює як сервіс systemd. PAM-ліміти застосовуються до сесій входу. systemd застосовує свої ресурси з unit-файлів і дефолтів.
2) Чи потрібно встановлювати одночасно LimitNOFILE і worker_rlimit_nofile?
Так, на практиці. LimitNOFILE — це ОС-застосований потолок для сервісу. worker_rlimit_nofile змушує Nginx явно запросити/поширити відповідний ліміт для воркерів. Узгодьте їх, щоб уникнути невизначеності «має бути нормально».
3) Який розумний LimitNOFILE для Nginx?
Звичайний: 65535. Проксі з великим трафіком можуть використовувати 131072. Підвищуйте вище лише з вимірюваною потребою і моніторингом, бо це може маскувати витоки і збільшувати площу ураження.
4) Якщо я підніму ліміт, чи Nginx автоматично оброблятиме більше з’єднань?
Не автоматично. Потрібні також відповідні worker_connections, CPU, пам’ять, upstream-місткість і іноді тюнінг ядра. Більше конкурентності без ресурсів — це просто більша черга для відмов.
5) Я підняв ліміти, але іноді все одно бачу «Too many open files». Чому?
Або ви не застосували ліміт до запущеного процесу (перевірте /proc), або ви легітимно досягаєте нового ліміту під сплесками, або інша компонента (upstream, резолвер, логування) несподівано споживає дескриптори. Вимірюйте FD по воркеру і корелюйте з трафіком і латентністю.
6) Чи може «Too many open files» бути спричинено багом або витоком?
Так. Якщо використання FD росте при відсутності росту трафіку, підозрюйте витік або неправильну конфігурацію, яка утримує дескриптори (надмірний keepalive, кешування, завислі upstream-з’єднання).
7) Чи безпечно змінювати LimitNOFILE без простою?
Зміна ліміту вимагає рестарту сервісу, що може викликати короткий перерив у роботі, якщо у вас немає кількох інстансів за балансувальником. Плануйте ротаційний рестарт, де це можливо.
8) Як налаштувати сповіщення про це до того, як воно стане аварією?
Сповіщайте за використанням FD як відсоток від ліміту на воркер (наприклад, 70% попередження, 85% критично). Ви можете обчислювати це з /proc/<pid>/fd та /proc/<pid>/limits.
9) Що робити, якщо системний file-max — вузьке місце?
Тоді у вас «вся хоста вичерпує дескриптори». Ідентифікуйте найбільших споживачів по процесах. Підняття лімітів Nginx не допоможе, якщо ядро не може виділити більше. Це рідко на сучасних Ubuntu, але можливо на щільних multi-tenant вузлах.
10) Чи зменшує HTTP/2 використання FD через мультиплексування?
Іноді так, але на це не варто покладатися. HTTP/2 може зменшити кількість клієнтських TCP-з’єднань, але поведінка upstream, пули keepalive і sidecar-и все одно можуть домінувати у використанні FD.
Висновок: наступні кроки, що працюють
Коли Nginx на Ubuntu 24.04 досягає «Too many open files», правильне рішення — не геройський one-liner. Це перевірений ланцюжок: systemd надає ліміт, Nginx налаштований використовувати його безпечно, і ядро має достатню глобальну ємність.
Зробіть це, у такому порядку:
- Перевірте живий ліміт через
/proc/<pid>/limits. Якщо це 1024 — зупиніться і виправтеLimitNOFILEв systemd. - Встановіть systemd drop-in override для
nginx.serviceі рестартуйте. Знову перевірте. - Узгодьте Nginx через
worker_rlimit_nofileі встановітьworker_connectionsна основі виміряної конкурентності, а не інтуїції. - Вимірюйте кількість FD на воркер під піком. Додайте сповіщення про запас. Якщо FD зростає з часом — шукайте витоки або повільні upstream.
Після цього ви зможете насолоджуватися рідкісною розкішшю веб-сервера, який падає через цікаві причини, а не через те, що у нього закінчилися нумеровані дескриптори.