دسته بندی:

php.ini اختصاصی برای php-fpm در دایرکت ادمین

پاسخ کوتاه: وقتی PHP روی سرور با PHP-FPM اجرا می‌شود، دستورهای php_value و php_flag داخل .htaccess دیگر کار نمی‌کنند، چون این دستورها متعلق به ماژول mod_php هستند نه به PHP-FPM. در دایرکت‌ادمین، تنظیمات اختصاصی هر کاربر باید در فایل /usr/local/directadmin/data/users/USERNAME/php/php-fpm.conf.custom2 نوشته شود و تنظیمات سبک‌تر در سطح یک سایت با فایل .user.ini کنار فایل‌های همان سایت اعمال می‌شود.

چرا php_value در .htaccess با PHP-FPM نادیده گرفته می‌شود

در معماری قدیمی، PHP به شکل یک ماژول داخل خود پروسه آپاچی اجرا می‌شد. آن ماژول بود که دستورهای php_value، php_flag، php_admin_value و php_admin_flag را به آپاچی معرفی می‌کرد؛ پس آپاچی می‌فهمید این خط‌ها یعنی چه و می‌توانست آن‌ها را در .htaccess بخواند.

در PHP-FPM داستان کاملاً فرق دارد. PHP در یک مجموعه پروسه جداگانه (pool) و با کاربر خودش اجرا می‌شود و وب‌سرور فقط درخواست را از طریق پروتکل FastCGI به آن تحویل می‌دهد. آپاچی در این حالت هیچ اطلاعی از تنظیمات PHP ندارد. نتیجه یکی از این دو حالت است: یا خط شما بی‌اثر می‌ماند، یا اگر ماژول mod_php اصلاً بارگذاری نشده باشد، آپاچی خطای Invalid command 'php_value' را در لاگ می‌نویسد و کل سایت با خطای ۵۰۰ بالا نمی‌آید.

نکته‌ای که باید در ۲۰۲۶ بدانید: بازگشت به mod_php راه‌حل نیست. دایرکت‌ادمین پشتیبانی از mod_php را از ابتدای ۲۰۲۳ کنار گذاشت و CustomBuild دیگر آن را نمی‌سازد؛ این حالت اجرا عملاً منسوخ شده است، چون همه سایت‌های سرور را با یک کاربر مشترک اجرا می‌کرد و از نظر امنیتی و ایزوله‌سازی منابع قابل دفاع نیست. آموزش‌های قدیمی که هنوز .htaccess را پیشنهاد می‌دهند یا مربوط به mod_php هستند یا مربوط به وب‌سرورهای LiteSpeed و OpenLiteSpeed که این دستورها را برای سازگاری شبیه‌سازی می‌کنند. این رفتار هم اختصاصی دایرکت‌ادمین نیست؛ در هر پنلی که PHP-FPM را جایگزین کرده یکسان است و فقط مسیر فایل‌ها فرق می‌کند. اگر هنوز پنل را روی سرور نصب نکرده‌اید، لایسنس دایرکت‌ادمین یکی از گزینه‌های رایج برای همین سناریو است.

چهار لایه تنظیمات و ترتیب اولویتشان

قبل از هر ویرایشی، بدانید تنظیم شما در کدام لایه می‌نشیند. اولویت از پایین به بالا است و هر لایه بعدی لایه قبلی را بازنویسی می‌کند:

  • فایل php.ini سراسری همان نسخه PHP، که پایه همه چیز است.
  • تنظیمات pool مربوط به کاربر، یعنی خط‌های php_value[...] و php_admin_value[...].
  • فایل .user.ini در مسیر سایت.
  • فراخوانی ini_set() داخل کد برنامه.

یک استثنای مهم: هر چیزی که با php_admin_value یا php_admin_flag تعریف شود قفل می‌شود و نه .user.ini و نه ini_set() نمی‌توانند آن را عوض کنند. این دقیقاً ابزاری است که برای بستن دست کاربر لازم دارید.

فایل اختصاصی هر کاربر در دایرکت‌ادمین

بزرگ‌ترین اشتباهی که در آموزش‌های فارسی و انگلیسی قدیمی تکرار می‌شود این است که می‌گویند فایل /usr/local/directadmin/data/users/USERNAME/php/php-fpm84.conf را ویرایش کنید. آن فایل تولیدی است: دایرکت‌ادمین آن را از روی قالب /usr/local/directadmin/data/templates/php-fpm.conf می‌سازد و در اولین بازنویسی کانفیگ‌ها یا اولین تغییر نسخه PHP، هر چه در آن نوشته باشید پاک می‌شود.

فایلی که ماندگار است و باید در آن بنویسید این است:

/usr/local/directadmin/data/users/USERNAME/php/php-fpm.conf.custom2

محتوای این فایل داخل توکن CUSTOM2 قالب قرار می‌گیرد و جای درست خط‌های php_value و php_admin_value است. مزیت این نام بدون شماره نسخه هم این است که برای همه نسخه‌های PHP کاربر اعمال می‌شود؛ اگر عمداً می‌خواهید تنظیم فقط روی یک نسخه سوار شود، مستندات دایرکت‌ادمین فایل نسخه‌دار همان کاربر یعنی php-fpm84.custom2 را مثال می‌زند؛ چون نام این فایل نسخه‌به‌نسخه یکسان نبوده، مطمئن‌ترین راه این است که مقدار را از خود پنل ذخیره کنید تا دایرکت‌ادمین فایل درست را بسازد و بعد نامش را در همان پوشه ببینید. برای اعمال یک تنظیم روی همه کاربران سرور هم فایل /usr/local/directadmin/data/templates/custom/php-fpm.conf.custom2 را بسازید.

اگر ترجیح می‌دهید دست به فایل نزنید، مسیر گرافیکی همین کار در پنل است: از سطح ادمین به Custom Httpd Configuration بروید و صفحه دامنه، بخش php-fpm همان نسخه را باز کنید. اینجا یک تفاوت مهم هست که اگر ندانیدش تنظیم‌تان سر جای اشتباه می‌نشیند: دو کادر بالایی با عنوان global php-fpm CUSTOM1 و global php-fpm CUSTOM2 روی همه نسخه‌های PHP آن کاربر اثر می‌گذارند و همان فایل بدون شماره نسخه بالا را می‌نویسند، اما دو کادر پایینی فقط به نسخه PHP همان صفحه مربوط‌اند. پس اگر تنظیم باید بعد از تعویض نسخه هم بماند، سراغ کادر global بروید. متن دستورهای php_value و php_admin_value در کادر CUSTOM2 می‌رود؛ کادر CUSTOM1 جای توکن‌های خود pool است، مثلاً |?MAX_CHILDREN=200| برای بالا بردن سقف پروسه‌های یک کاربر.

نمونه محتوای فایل:

php_admin_value[disable_functions] = exec,passthru,shell_exec,proc_open,popen

php_admin_flag[allow_url_fopen] = off

php_admin_value[open_basedir] = /home/USERNAME/:/tmp/

php_value[memory_limit] = 512M

php_value[upload_max_filesize] = 128M

php_value[post_max_size] = 128M

تفاوت دو خانواده را جدی بگیرید. php_value یک پیش‌فرض است که کاربر می‌تواند بعداً بالاتر یا پایین‌تر ببرد؛ برای چیزهایی مثل memory_limit منطقی است. اما php_admin_value یک سقف اجباری است و تنها راه تنظیم دستورهایی مثل disable_functions است که سطحشان PHP_INI_SYSTEM است. open_basedir حساب جداگانه‌ای دارد: سطحش PHP_INI_ALL است ولی PHP اجازه می‌دهد فقط تنگ‌تر شود و هیچ‌وقت بازتر نشود؛ با این حال اگر می‌خواهید کاربر نتواند مقدارش را دست‌کاری کند، باز هم باید با php_admin_value بنویسیدش.

یک محدودیت مهم که وقت زیادی می‌گیرد: در PHP-FPM فهرست disable_functions فقط اضافه‌شدنی است. هر چه در php.ini سراسری بسته شده باشد، با نوشتن یک فهرست کوتاه‌تر در سطح pool باز نمی‌شود؛ خط شما فقط به فهرست موجود اضافه می‌کند. اگر واقعاً می‌خواهید یک تابع را برای یک کاربر خاص باز کنید، باید ابتدا آن را از php.ini سراسری بردارید و بعد فهرست پایه را برای بقیه کاربران از راه قالب سراسری برگردانید. برای این کارها به دسترسی root نیاز دارید؛ روی یک سرور مجازی ایران با دسترسی مدیریتی کامل این فایل‌ها در اختیار خودتان است.

اعمال تغییرات و ری‌لود کردن FPM

بعد از ذخیره فایل، دایرکت‌ادمین باید کانفیگ‌ها را دوباره بسازد و بعد سرویس FPM بارگذاری شود:

da build rewrite_confs

systemctl reload php-fpm84

دستور da رابط خط فرمان امروزی دایرکت‌ادمین است؛ معادل قدیمی آن یعنی cd /usr/local/directadmin/custombuild && ./build rewrite_confs هنوز کار می‌کند ولی روش توصیه‌شده نیست.

برای اینکه مطمئن شوید تنظیم واقعاً به فایل تولیدی راه پیدا کرده، همان فایل pool کاربر را بخوانید؛ این تنها راستی‌آزمایی مطمئن است، چون نشان می‌دهد قالب خط شما را برداشته یا نه:

grep -n "disable_functions" /usr/local/directadmin/data/users/USERNAME/php/php-fpm84.conf

اگر سرویس بالا نیامد یا سایت‌ها خطا دادند، لاگ سرویس همه چیز را می‌گوید:

systemctl status php-fpm84

journalctl -u php-fpm84 -n 50 --no-pager

ترجیح بدهید reload را به جای restart استفاده کنید؛ اگر کانفیگ ایراد نحوی داشته باشد، پروسه‌های قبلی سر جایشان می‌مانند و سایت‌ها قطع نمی‌شوند. فقط وقتی یک pool کاملاً تازه ساخته شده یا سرویس تغییر را برنداشت، سراغ systemctl restart php-fpm84 بروید.

فایل .user.ini برای تنظیمات در سطح یک سایت

اگر دسترسی root ندارید یا فقط می‌خواهید یک سایت خاص تنظیم متفاوتی داشته باشد، فایل .user.ini جایگزین درست .htaccess است. این قابلیت با دستور user_ini.filename کنترل می‌شود و در دایرکت‌ادمین به‌صورت پیش‌فرض فعال است. فایل را در ریشه سایت بسازید، مثلاً /home/USERNAME/domains/example.com/public_html/.user.ini، با محتوایی شبیه این:

upload_max_filesize = 64M

post_max_size = 64M

max_execution_time = 120

max_input_vars = 5000

دو رفتار این فایل را بدانید. اول اینکه PHP از پوشه اسکریپت اجراشده تا ریشه سند به سمت بالا فایل‌ها را می‌خواند، پس یک فایل در ریشه سایت روی زیرپوشه‌ها هم اثر دارد. دوم اینکه محتوای آن کش می‌شود و طبق مقدار user_ini.cache_ttl که به‌صورت پیش‌فرض ۳۰۰ ثانیه است، تغییر شما تا پنج دقیقه ممکن است دیده نشود. عجله نکنید و سرویس را بیهوده ری‌استارت نکنید.

یک هشدار درباره max_execution_time: در PHP-FPM حرف آخر را request_terminate_timeout در تنظیمات pool می‌زند. اگر آن مقدار کمتر از عددی باشد که در .user.ini نوشته‌اید، FPM پروسه را زودتر می‌کشد و شما در لاگ خطای 504 یا پیام قطع‌شدن اسکریپت می‌بینید بدون اینکه ربطی به تنظیم شما داشته باشد.

روی هاست اشتراکی همین فایل عملاً تنها ابزار شماست، چون هیچ‌کدام از فایل‌های سطح سرور در دسترستان نیست. یک نکته را هم در نظر بگیرید: سرویس‌هایی مثل هاست لینوکس ایران که روی وب‌سرور LiteSpeed اجرا می‌شوند بخشی از دستورهای .htaccess را همچنان می‌پذیرند، پس رفتاری که آنجا می‌بینید لزوماً با یک سرور آپاچی و PHP-FPM یکسان نیست.

کدام دستور کجا قابل تنظیم است

دستور سطح تغییرپذیری جای درست تنظیم
memory_limit PHP_INI_ALL هر سه لایه، حتی ini_set()
max_execution_time PHP_INI_ALL هر سه لایه
upload_max_filesize PHP_INI_PERDIR pool یا .user.ini
post_max_size PHP_INI_PERDIR pool یا .user.ini
max_input_vars PHP_INI_PERDIR pool یا .user.ini
disable_functions PHP_INI_SYSTEM php_admin_value در pool، فقط برای افزودن
open_basedir PHP_INI_ALL ولی فقط قابل تنگ‌تر شدن برای اجبار، php_admin_value در pool
extension و بیشتر opcache.* PHP_INI_SYSTEM فقط php.ini یا فایل‌های conf.d

قاعده ساده: هر چه به امنیت یا به بارگذاری موتور PHP مربوط است در سطح pool یا php.ini جا دارد و هرگز با .user.ini عوض نمی‌شود.

راستی‌آزمایی و عیب‌یابی

برای دیدن مقدار واقعی، یک فایل موقت با محتوای <?php phpinfo(); در سایت بسازید و از طریق مرورگر بازش کنید، سپس بلافاصله حذفش کنید. اجرای php -i در SSH گمراه‌کننده است، چون نسخه خط فرمان فایل php.ini دیگری می‌خواند و اصلاً از تنظیمات pool خبر ندارد؛ با php --ini می‌توانید ببینید همان نسخه خط فرمان دقیقاً کدام فایل را بار کرده است. اگر فقط یک مقدار را می‌خواهید، ini_get('memory_limit') کافی است.

قبل از هر عیب‌یابی، اول مشخص کنید سایت شما با کدام روش اجرای PHP بالا می‌آید. صفحه phpinfo() این را در فیلد Server API نشان می‌دهد: مقدار FPM/FastCGI یعنی تنظیم را باید در pool یا .user.ini بگذارید و هر خطی که در .htaccess نوشته‌اید بی‌اثر است. اگر روی هاست اشتراکی هستید و به این لایه دسترسی ندارید، ابزار انتخاب نسخه PHP در کنترل پنل تنها راه شماست. تعریف نوع فایل‌ها هم از همین پنل انجام می‌شود و Mime Types در دایرکت ادمین آن بخش را نشان می‌دهد.

پیشگیری از تکرار مشکل

سه عادت ساده جلوی بیشتر دردسرها را می‌گیرد. اول، تنظیم‌ها را در فایل بدون شماره نسخه یعنی php-fpm.conf.custom2 یا در کادرهای global پنل نگه دارید؛ اگر آن‌ها را فقط برای یک نسخه خاص ذخیره کرده باشید، به‌محض اینکه کاربر نسخه PHP را عوض کند تنظیم‌ها بی‌اثر می‌شوند. ضمناً نسخه‌هایی مثل PHP 8.1 که دوره پشتیبانی امنیتی‌شان تمام شده دیگر وصله امنیتی نمی‌گیرند و ماندن رویشان به‌خودی‌خود یک ریسک است؛ CustomBuild امروز حتی ساخت PHP 8.5 را هم پشتیبانی می‌کند، هرچند خود دایرکت‌ادمین هنوز آن را برای نقش نسخه اصلی روی سرور عملیاتی توصیه نمی‌کند و نسخه پایدارتر برای این نقش منطقی‌تر است. دوم، فهرست disable_functions را کورکورانه کپی نکنید؛ بستن توابعی مثل proc_open می‌تواند نصب افزونه‌ها یا کرون‌های برنامه را از کار بیندازد، پس بعد از هر تغییر یک بار مسیرهای اصلی سایت را تست کنید. سوم، این فایل‌ها را جایی مستند و نسخه‌برداری کنید تا بعد از انتقال سرور از نو کشفشان نکنید.

و اگر ترجیح می‌دهید درگیر این لایه از تنظیمات نشوید، سرور مجازی مدیریت‌شده همین کارها را به تیم پشتیبانی می‌سپارد و شما فقط نیازتان را اعلام می‌کنید.