خطای Too many open files معمولاً در بدترین زمان ظاهر میشود: در اوج ترافیک، وقتی Nginx ناگهان 500 برمیگرداند، MySQL در لاگ خطای «Can't open file» مینویسد یا Node.js با EMFILE کرش میکند. ریشهٔ همهٔ اینها یک چیز است: محدودیتی که ulimit یا systemd بر تعداد file descriptorهای باز هر پروسه گذاشته و پیشفرض آن، 1024، برای هر سرور جدی ناکافی است. در این مقاله میبینیم file descriptor چیست، سه سطح محدودیت (پروسه، سرویس و کرنل) کجا تنظیم میشوند، چرا تغییر limits.conf روی سرویسهای systemd اثر ندارد و چگونه برای Nginx، MySQL، PHP-FPM و Redis مقدار درست را تعیین کنیم.
خطای Too many open files دقیقاً یعنی چه؟
در لینوکس «همه چیز فایل است»: هر فایل باز، هر سوکت شبکه، هر pipe و هر اتصال به دیتابیس یک file descriptor (fd) مصرف میکند که عددی صحیح در جدول پروسه است. وبسروری که 5000 اتصال همزمان دارد و برای هر کدام یک اتصال به PHP-FPM باز میکند، به بیش از 10 هزار fd نیاز دارد؛ با سقف 1024، تنها پس از چند صد کاربر همزمان خطا شروع میشود. هر فایل لاگ باز، هر فایل جدول InnoDB، هر watcher inotify و هر epoll نیز یک descriptor است.
EMFILE در برابر ENFILE: دو سطح مختلف
کرنل دو خطای متفاوت میدهد که هر دو به یک پیام ترجمه میشوند. EMFILE یعنی «این پروسه» به سقف خودش (ulimit -n) رسیده و ENFILE یعنی «کل سیستم» به fs.file-max رسیده است. اولی بسیار رایجتر است و با تنظیم سرویس حل میشود؛ دومی نادر است و معمولاً نشانهٔ نشت descriptor در یکی از پروسههاست. با cat /proc/sys/fs/file-nr سه عدد میبینید: fdهای تخصیصیافته، آزاد و سقف سیستم؛ اگر عدد اول به سومی نزدیک است، مشکل سطح سیستم دارید.
ulimit: محدودیتهای هر پروسه و تفاوت soft با hard
ulimit یک دستور داخلی shell است که محدودیتهای منابع (resource limits) پروسهٔ فعلی و فرزندان آن را نشان میدهد یا تغییر میدهد. هر محدودیت دو مقدار دارد: soft که مقدار مؤثر فعلی است و پروسه میتواند خودش تا سقف hard بالا ببرد، و hard که فقط root میتواند افزایش دهد. ulimit -Sn و ulimit -Hn این دو را برای descriptor نشان میدهند؛ ulimit -a همهٔ محدودیتها را فهرست میکند.
دیدن محدودیت یک پروسهٔ در حال اجرا
اجرای ulimit در shell شما، محدودیت پروسهٔ Nginx را نشان نمیدهد. برای یک پروسهٔ مشخص باید فایل /proc/PID/limits را بخوانید که ستونهای Soft Limit و Hard Limit را برای هر منبع دارد. این فایل منبع حقیقت است و اگر عددی که انتظار دارید آنجا نیست، تغییر شما جایی اعمال نشده است.
pgrep -o nginx # PID پروسهٔ اصلی
grep 'open files' /proc/$(pgrep -o nginx)/limits
ls /proc/$(pgrep -o nginx)/fd | wc -l # تعداد fd باز همین حالا
prlimit --pid $(pgrep -o mysqld) --nofile=65535:65535 # تغییر بدون ریاستارت
دستور prlimit (بخشی از util-linux) محدودیت یک پروسهٔ در حال اجرا را بدون ریاستارت تغییر میدهد و برای رفع اضطراری مشکل روی سرور زنده بسیار ارزشمند است؛ اما پس از ریاستارت سرویس، مقدار دوباره از پیکربندی دائمی خوانده میشود.
سه جای تنظیم: limits.conf، systemd و sysctl
مهمترین نکتهٔ این مقاله همینجاست: محدودیتها در سه سطح مستقل اعمال میشوند و تنظیم یکی، روی دیگری اثر ندارد.
| محل تنظیم | روی چه چیزی اثر دارد | روی چه چیزی اثر ندارد |
|---|---|---|
| /etc/security/limits.conf | نشستهای login (SSH، su -، cron با PAM) | سرویسهای systemd |
| LimitNOFILE در unit سرویس | همان سرویس و فرزندانش | کاربران SSH |
| DefaultLimitNOFILE در system.conf | همهٔ سرویسهای بدون تنظیم صریح | نشستهای کاربر |
| fs.nr_open (sysctl) | سقف مجاز برای hard limit هر پروسه | مقدار مؤثر پروسهها |
| fs.file-max (sysctl) | سقف کل سیستم | محدودیت تکپروسه |
/etc/security/limits.conf و نقش PAM
این فایل توسط ماژول pam_limits در لحظهٔ ساخت نشست login خوانده میشود. فرمت آن چهار ستون است: دامنه (نام کاربر، @گروه یا *)، نوع (soft/hard یا - برای هر دو)، نام منبع (nofile، nproc، core) و مقدار. خط «nginx soft nofile 65535» تنها زمانی اثر دارد که پروسه از یک نشست PAM آغاز شود؛ سرویسی که systemd هنگام بوت بالا میآورد، هرگز از PAM عبور نمیکند و این فایل را نمیبیند. به همین دلیل بسیاری پس از ویرایش limits.conf و ریاستارت Nginx، هنوز خطا میگیرند. برای اعمال روی نشست SSH باید UsePAM yes در sshd_config فعال باشد.
LimitNOFILE در systemd
راه درست برای سرویسها، فایل override است تا با بهروزرسانی بسته بازنویسی نشود:
systemctl edit nginx
# در ویرایشگر:
[Service]
LimitNOFILE=65535
# سپس:
systemctl daemon-reload && systemctl restart nginx
systemd از نسخهٔ 240 به بعد hard limit پیشفرض سرویسها را 524288 و soft را 1024 قرار میدهد؛ بنابراین بسیاری از برنامههای مدرن (Go، Node 20 به بعد) خودشان soft را تا hard بالا میبرند، اما برنامههای قدیمیتر به همان 1024 گیر میکنند. برای تغییر سراسری، DefaultLimitNOFILE=65535:524288 را در /etc/systemd/system.conf بگذارید و daemon-reexec کنید.
سطح کرنل: fs.nr_open و fs.file-max
fs.nr_open (پیشفرض 1048576) حداکثر مقداری است که میتوان بهعنوان hard limit یک پروسه تعیین کرد؛ اگر LimitNOFILE بزرگتر از آن بگذارید، سرویس اصلاً شروع نمیشود. fs.file-max در کرنلهای اخیر بهطور خودکار عددی بسیار بزرگ است و بهندرت نیاز به تغییر دارد.
تنظیم درست برای سرویسهای رایج
Nginx و PHP-FPM
Nginx دستور worker_rlimit_nofile دارد که خودش soft limit را تا این مقدار بالا میبرد (به شرط آنکه hard اجازه دهد). قاعدهٔ سرانگشتی: worker_rlimit_nofile باید دستکم دو برابر worker_connections باشد، چون هر اتصال پراکسیشده دو fd میخواهد. PHP-FPM در پیکربندی هر pool گزینهٔ rlimit_files دارد و برای سایتهای شلوغ 65535 منطقی است.
MySQL و MariaDB
MySQL هنگام شروع میکوشد open_files_limit را از my.cnf اعمال کند، اما بیشتر از hard limit پروسه نمیتواند برود؛ در لاگ خطا پیام «Could not increase number of max_open_files» دقیقاً همین را میگوید. unit رسمی MariaDB مقدار LimitNOFILE=32768 را دارد که برای سرورهای هاست با هزاران جدول باید با override به 65535 یا بیشتر افزایش یابد. table_open_cache را هم متناسب با آن تنظیم کنید.
Redis، Node.js و Elasticsearch
Redis هنگام شروع بررسی میکند که nofile حداقل maxclients + 32 باشد و در غیر این صورت maxclients را خودکار کاهش میدهد و در لاگ هشدار مینویسد. Elasticsearch و OpenSearch بدون 65535 حتی شروع نمیشوند. برای اپلیکیشنهای Node یا Python که با PM2 یا Gunicorn مدیریت میشوند، محدودیت از unit systemd خود process manager به ارث میرسد و در Docker با --ulimit nofile=65535:65535 تعیین میشود.
وقتی limit کافی است اما خطا ادامه دارد: نشت descriptor
اگر بعد از افزایش به 65535 باز هم پس از چند روز خطا برمیگردد، احتمالاً برنامهای فایلها یا سوکتها را باز میکند و نمیبندد. علامت مشخص آن رشد خطی تعداد fd در طول زمان است که با ls /proc/PID/fd | wc -l هر ساعت قابل مشاهده است. دستور lsof -p PID نوع fdهای باز را نشان میدهد؛ هزاران سوکت در وضعیت CLOSE_WAIT یعنی برنامه پاسخ بسته شدن اتصال را دریافت کرده اما close را صدا نزده؛ هزاران فایل لاگ یکسان یعنی logger هر بار فایل جدیدی باز میکند. افزایش محدودیت در این حالت فقط زمان کرش را عقب میاندازد؛ راهحل، اصلاح کد یا ریاستارت زمانبندیشده تا رفع باگ است. اگر چنین عیبیابیهایی را نمیخواهید خودتان انجام دهید، سرویس مدیریت سرور این کار را بر عهده میگیرد.
سایر محدودیتهایی که سرور را زمین میزنند
nofile تنها محدودیت نیست. nproc (ulimit -u) حداکثر تعداد پروسه یا نخ یک کاربر است و خطای «fork: retry: Resource temporarily unavailable» را میدهد؛ روی سرورهای هاست اشتراکی با کاربران زیاد، تنظیم آن برای هر کاربر، ابزار محدودسازی مفیدی است. معادل آن در systemd گزینهٔ TasksMax است که پیشفرض آن 15 درصد pid_max است و برای سرویسهای چندنخی مانند Java یا PHP-FPM با صدها فرزند میتواند کم باشد. core (ulimit -c) اندازهٔ فایل core dump را تعیین میکند و برای عیبیابی کرش گاهی باید از صفر بیرون بیاید. همهٔ اینها با همان الگوی LimitXXX در systemd (LimitNPROC، LimitCORE) تنظیم میشوند. روی هاست پرترافیک این مقادیر از پیش توسط مدیر سرور تنظیم شدهاند و کاربر هاست درگیر آنها نمیشود.
جمعبندی
خطای Too many open files تقریباً همیشه با پیشفرض 1024 descriptor شروع میشود و راهحل آن شناخت سه سطح محدودیت است: ulimit و limits.conf برای نشستهای login، LimitNOFILE برای سرویسهای systemd و fs.nr_open در کرنل. برای سرویسها از override در systemd استفاده کنید، مقدار مؤثر را در /proc/PID/limits تأیید کنید و اگر خطا با وجود محدودیت کافی تکرار شد، به دنبال نشت descriptor بگردید نه عدد بزرگتر.
سوالات متداول
چرا ulimit -n را تغییر دادم ولی Nginx هنوز خطا میدهد؟
ulimit فقط روی shell فعلی و پروسههایی که از آن اجرا میشوند اثر دارد. Nginx توسط systemd اجرا میشود و محدودیت خود را از LimitNOFILE در unit میگیرد. با systemctl edit nginx مقدار را تنظیم کنید و بعد از daemon-reload سرویس را ریاستارت کنید.
مقدار مناسب nofile برای سرور وب چقدر است؟
برای اغلب سرورها 65535 کافی است و هزینهای ندارد، چون descriptorها فقط هنگام استفاده حافظه میگیرند. برای پراکسیها یا سرورهای WebSocket با دهها هزار اتصال همزمان، 262144 یا بیشتر منطقی است، به شرط آنکه fs.nr_open اجازه دهد.
چطور بفهمم کدام پروسه بیشترین فایل باز را دارد؟
یک حلقهٔ ساده روی /proc/*/fd تعداد fd هر پروسه را میشمارد و با sort میتوانید پرمصرفترین را ببینید. دستور lsof نیز همین کار را میکند اما روی سرورهای شلوغ کند است.
آیا تنظیم nofile روی مقدار بسیار بزرگ خطرناک است؟
به خودی خود نه، اما محدودیت یک سازوکار حفاظتی است: پروسهای که نشت دارد، با محدودیت بزرگتر بهجای کرش سریع، بهتدریج حافظهٔ کرنل را مصرف میکند. مقدار معقول انتخاب کنید و مصرف را مانیتور کنید.
در هاست اشتراکی cPanel این خطا را چگونه رفع کنیم؟
کاربر هاست اشتراکی به این تنظیمات دسترسی ندارد؛ خطا معمولاً از افزونه یا اسکریپتی ناشی میشود که فایلها را نمیبندد. آن را شناسایی و غیرفعال کنید و در صورت تکرار، از پشتیبانی هاست بخواهید محدودیت سرور را بررسی کند.