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.ini بنشیند، جایش پوشه php.conf.d همان نسخه PHP است.
چرا php_value در .htaccess با PHP-FPM نادیده گرفته میشود
در معماری قدیمی، PHP به شکل یک ماژول (mod_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 بهعلاوه فایلهای.iniداخل پوشه اسکن همان نسخه (php.conf.d)، که پایه همه چیز است. - تنظیمات pool مربوط به کاربر، یعنی خطهای
php_value[...]وphp_admin_value[...]. - فایل
.user.iniدر مسیر سایت. - فراخوانی
ini_set()داخل کد برنامه.
یک استثنای مهم: هر چیزی که با php_admin_value یا php_admin_flag تعریف شود قفل میشود و نه .user.ini و نه ini_set() نمیتوانند آن را عوض کنند. این دقیقاً ابزاری است که برای بستن دست کاربر لازم دارید.
لایه php.conf.d و تنظیم بر اساس دامنه یا مسیر
لایه پایینی فقط خود فایل php.ini نیست. PHP هنگام بالا آمدن یک پوشه اسکن هم دارد و همه فایلهای با پسوند .ini داخل آن را میخواند. در دایرکتادمین که هر نسخه PHP جداگانه با CustomBuild ساخته میشود، این پوشه کنار همان نسخه است:
/usr/local/php84/lib/php.conf.d/
شماره داخل مسیر همان نسخه PHP است؛ روی سرورهای قدیمی php56 بود و امروز php82 یا php84. به جای حدس زدن، مسیر واقعی را از خود PHP بپرسید؛ خروجی این دستور هم فایل php.ini و هم پوشه اسکن را نشان میدهد:
/usr/local/php84/bin/php --ini
در صفحه phpinfo() هم همین دو مقدار با عنوان Scan this dir for additional .ini files و Additional .ini files parsed دیده میشوند و مهمتر از مسیر، همان خط دوم است که ثابت میکند فایل شما واقعاً خوانده شده.
روی سرورهای mod_php از همین لایه برای تنظیم دامنهبهدامنه استفاده میشد و هنوز هم کار میکند، چون بخشهای [HOST=...] و [PATH=...] بخشی از خود PHP هستند نه ماژول آپاچی. یک فایل با نام دلخواه در پوشه بالا بسازید، مثلاً 30-example.com.ini، با این محتوا:
[HOST=example.com] post_max_size = 128M max_execution_time = 120 session.save_path = /home/USERNAME/tmp
به جای example.com نام دامنه و به جای USERNAME نام کاربری دایرکتادمین را بگذارید. اگر مسیر موقتی جدا تعریف میکنید، اول مطمئن شوید پوشه /home/USERNAME/tmp وجود دارد و مالکش همان کاربر است، وگرنه session سایت از کار میافتد. تطبیق [HOST=...] بر اساس نام میزبان درخواست انجام میشود، پس اگر سایت هم با www و هم بدون آن بالا میآید، یا باید یکی را به دیگری ریدایرکت کنید یا برای هر دو یک بخش جدا بنویسید. برای محدود کردن تنظیم به یک مسیر به جای یک دامنه هم از این شکل استفاده کنید:
[PATH=/home/USERNAME/domains/example.com/public_html] max_execution_time = 120
یک محدودیت که در آموزشهای قدیمی هیچوقت گفته نشده و باعث میشود ساعتها دنبال تنظیمی بگردید که «اعمال نمیشود»: داخل بخشهای [HOST=...] و [PATH=...] فقط دستورهایی اثر میکنند که سطحشان PHP_INI_PERDIR یا PHP_INI_USER (یعنی PHP_INI_ALL) باشد. دستورهای سطح PHP_INI_SYSTEM مثل disable_functions، upload_tmp_dir، extension و بیشتر opcache.* فقط موقع بالا آمدن موتور خوانده میشوند و اگر داخل این بخشها بنویسیدشان بیسروصدا نادیده گرفته میشوند؛ نه خطایی میبینید و نه اثری. نمونههای قدیمی که disable_functions را زیر [HOST=domain.com] گذاشتهاند دقیقاً به همین دلیل هرگز کار نکردهاند. این دستورها یا باید بیرون از هر بخش و در سطح کل آن نسخه PHP نوشته شوند، یا برای یک کاربر خاص با php_admin_value در فایل pool که در بخش بعد میآید.
دو نکته دیگر که در آموزشهای قدیمی اشتباه نوشته شده و گران تمام میشود. اول، نام فایل هیچ چیزی را محدود نمیکند؛ فایلی به اسم 30-USERNAME.ini بهخودیخود ربطی به آن کاربر ندارد. تنها چیزی که دامنه محدودیت را تعیین میکند همان سرتیتر [HOST=...] یا [PATH=...] است و هر خطی که بیرون از این بخشها بنویسید روی همه سایتهای آن نسخه PHP اعمال میشود. دوم، عدد ابتدای نام فایل فقط ترتیب خواندن است: فایلها به ترتیب الفبایی خوانده میشوند و هر فایل بعدی میتواند مقدار فایل قبلی را بازنویسی کند. پس عدد کوچکتر اولویت بیشتر نمیدهد؛ برعکس، فایلی که دیرتر خوانده شود (عدد بزرگتر) حرف آخر را میزند. فایلهای بدون پسوند .ini هم اصلاً خوانده نمیشوند.
امروز روی یک سرور PHP-FPM این لایه را برای چیزهایی نگه دارید که واقعاً جای دیگری تنظیم نمیشوند: بارگذاری اکستنشنها، بیشتر مقادیر opcache.* و upload_tmp_dir — همه بیرون از هر بخش [HOST=...]. برای تنظیم یک کاربر خاص، فایل pool همان کاربر هم امنتر است و هم توسط خود پنل مدیریت میشود. مخصوصاً disable_functions را در این پوشه ننویسید مگر بخواهید روی کل آن نسخه PHP و همه کاربران اعمال شود؛ برای یک کاربر، جای درستش php_admin_value در pool است.
فایل اختصاصی هر کاربر در دایرکتادمین
بزرگترین اشتباهی که در آموزشهای فارسی و انگلیسی قدیمی تکرار میشود این است که میگویند فایل /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
این نمونه را کورکورانه کپی نکنید. هم open_basedir و هم disable_functions از آن دسته تنظیمهایی هستند که اگر تنگتر از نیاز سایت بسته شوند، سایت زنده را با خطای open_basedir restriction in effect یا از کار افتادن یک افزونه پایین میآورند. اول روی یک سایت کماهمیت امتحان کنید، مسیرهای اصلی را باز کنید و بعد سراغ بقیه کاربران بروید.
تفاوت دو خانواده را جدی بگیرید. 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 هنوز کار میکند ولی روش توصیهشده نیست. توجه کنید که rewrite_confs کانفیگ همه کاربران سرور را از نو میسازد، پس هر ویرایش دستی که روی فایلهای تولیدی انجام داده باشید همانجا از بین میرود؛ این دقیقاً دلیل نوشتن تنظیم در فایل custom2 است. اگر تغییرتان در پوشه php.conf.d بوده، مرحله rewrite_confs لازم نیست ولی همان reload سرویس FPM را حتماً بزنید تا پروسههای تازه فایل را بخوانند؛ برای اجرای خط فرمان (CLI) هیچ ریاستارتی لازم نیست و اجرای بعدی خودش مقدار جدید را میگیرد. روی سرورهای قدیمیِ mod_php معادل این کار ریاستارت آپاچی بود؛ آنجا هم امروز روی AlmaLinux و Rocky و Ubuntu همان systemctl ابزار درست است و شکل قدیمی service httpd restart فقط یک پوسته سازگاری است که در نهایت به systemd تحویل داده میشود.
برای اینکه مطمئن شوید تنظیم واقعاً به فایل تولیدی راه پیدا کرده، همان فایل 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 بروید و بدانید که restart چند لحظه قطعی روی همه سایتهای آن نسخه PHP دارد.
فایل .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 که بهصورت پیشفرض ۳۰۰ ثانیه است، تغییر شما تا پنج دقیقه ممکن است دیده نشود. عجله نکنید و سرویس را بیهوده ریاستارت نکنید. اینجا هم برخلاف فایلهای php.conf.d، فقط خطهای ساده کلید = مقدار بنویسید؛ خود فایل به همان مسیر محدود است و سرتیتر [HOST=...] یا [PATH=...] در آن معنایی ندارد. ضمناً در .user.ini هم مثل بخشهای [HOST=...] فقط دستورهای سطح PHP_INI_PERDIR و PHP_INI_USER پذیرفته میشوند، پس چیزی مثل disable_functions را آنجا ننویسید چون بیاثر است.
یک هشدار درباره 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 | هر سه لایه و بخش [HOST=...] |
session.save_path |
PHP_INI_ALL | هر سه لایه؛ پوشه باید از قبل ساخته و متعلق به همان کاربر باشد |
upload_max_filesize |
PHP_INI_PERDIR | pool یا .user.ini یا بخش [HOST=...] |
post_max_size |
PHP_INI_PERDIR | pool یا .user.ini یا بخش [HOST=...] |
max_input_vars |
PHP_INI_PERDIR | pool یا .user.ini |
disable_functions |
PHP_INI_SYSTEM | php_admin_value در pool، فقط برای افزودن؛ در [HOST=...] و .user.ini بیاثر |
upload_tmp_dir |
PHP_INI_SYSTEM | pool با php_admin_value، یا php.conf.d بیرون از هر بخش |
open_basedir |
PHP_INI_ALL ولی فقط قابل تنگتر شدن | برای اجبار، php_admin_value در pool |
extension و بیشتر opcache.* |
PHP_INI_SYSTEM | فقط php.ini یا فایلهای php.conf.d، بیرون از هر بخش |
قاعده ساده: هر چه به امنیت یا به بارگذاری موتور PHP مربوط است در سطح pool یا php.ini جا دارد و هرگز با .user.ini عوض نمیشود.
راستیآزمایی و عیبیابی
برای دیدن مقدار واقعی، یک فایل موقت با محتوای <?php phpinfo(); در سایت بسازید و از طریق مرورگر بازش کنید، سپس بلافاصله حذفش کنید. اجرای php -i در SSH گمراهکننده است، چون نسخه خط فرمان فایل php.ini دیگری میخواند و اصلاً از تنظیمات pool خبر ندارد؛ با php --ini میتوانید ببینید همان نسخه خط فرمان دقیقاً کدام فایلها را بار کرده است. اگر فقط یک مقدار را میخواهید، ini_get('memory_limit') کافی است. همین تفاوت درباره بخشهای [HOST=...] هم صدق میکند: چون تطبیق آنها به نام میزبان درخواست وابسته است، در اجرای خط فرمان اثری از آنها نمیبینید و راستیآزمایی باید از طریق مرورگر و روی همان دامنه انجام شود.
قبل از هر عیبیابی، اول مشخص کنید سایت شما با کدام روش اجرای PHP بالا میآید. صفحه phpinfo() این را در فیلد Server API نشان میدهد: مقدار FPM/FastCGI یعنی تنظیم را باید در pool یا .user.ini بگذارید و هر خطی که در .htaccess نوشتهاید بیاثر است. اگر روی هاست اشتراکی هستید و به این لایه دسترسی ندارید، ابزار انتخاب نسخه PHP در کنترل پنل تنها راه شماست. تعریف نوع فایلها هم از همین پنل انجام میشود و Mime Types در دایرکت ادمین آن بخش را نشان میدهد.
پیشگیری از تکرار مشکل
چند عادت ساده جلوی بیشتر دردسرها را میگیرد. اول، تنظیمها را در فایل بدون شماره نسخه یعنی php-fpm.conf.custom2 یا در کادرهای global پنل نگه دارید؛ اگر آنها را فقط برای یک نسخه خاص ذخیره کرده باشید، بهمحض اینکه کاربر نسخه PHP را عوض کند تنظیمها بیاثر میشوند. همین تله برای پوشه php.conf.d جدیتر است، چون مسیرش نسخهبهنسخه فرق میکند: هر چه در /usr/local/php82/lib/php.conf.d/ نوشتهاید بعد از مهاجرت به php84 خوانده نمیشود و باید دستی منتقلش کنید. ضمناً نسخههایی مثل PHP 8.1 که دوره پشتیبانی امنیتیشان تمام شده دیگر وصله امنیتی نمیگیرند و ماندن رویشان بهخودیخود یک ریسک است؛ CustomBuild امروز حتی ساخت PHP 8.5 را هم پشتیبانی میکند، هرچند خود دایرکتادمین هنوز آن را برای نقش نسخه اصلی روی سرور عملیاتی توصیه نمیکند و نسخه پایدارتر برای این نقش منطقیتر است. دوم، فهرست disable_functions را کورکورانه کپی نکنید؛ بستن توابعی مثل proc_open میتواند نصب افزونهها یا کرونهای برنامه را از کار بیندازد، پس بعد از هر تغییر یک بار مسیرهای اصلی سایت را تست کنید. سوم، اگر از قالبهای قدیمی کپی میکنید حواستان به توابعی باشد که در PHP 8 دیگر وجود ندارند؛ نوشتن نام تابعی مثل create_function یا each در disable_functions امروز فقط یک خط بیاثر است. چهارم، قبل از دست زدن به این فایلها یک کپی از نسخه فعلیشان بردارید و جایی مستندشان کنید تا هم برگرداندن تغییر یک دستور ساده باشد و هم بعد از انتقال سرور از نو کشفشان نکنید.
و اگر ترجیح میدهید درگیر این لایه از تنظیمات نشوید، سرور مجازی مدیریتشده همین کارها را به تیم پشتیبانی میسپارد و شما فقط نیازتان را اعلام میکنید.
