فرض کنید فایلهای PHP را روی سرور گذاشتهاید، هش فایلها را هم گرفتهاید و برابر است، ولی رفتار سایت همان رفتار قبلی است. اگر کشِ bytecode مقایسهٔ زمانی فایلها را خاموش کرده باشد، همانطور که در مقالهٔ قبلیِ همین خوشه دربارهٔ تنظیمِ validate_timestamps توضیح دادهام، هیچ تغییر فایلی تا لحظهای که خودتان حافظه را خالی نکنید دیده نمیشود. راهحل، بستن پروسههای PHP همان اکانت است؛ عملیاتی که روی کاغذ یک خط دستور بیشتر نیست و در عمل، منبع چند ساعت سردرگمی میشود.
مشکل این است که اولین چیزی که برای دیدن آن پروسهها به کار میبریم، همان چیزی است که شما را گمراه میکند: فهرست پروسهها. روی این ترکیب نرمافزاری، ps فقط یک کلمه برای پروسه چاپ میکند، فیلتر کردن روی مسیر کامل نتیجهٔ خالی میدهد، pidof برای نام سرویسی که فرض میکنید چیزی برنمیگرداند، و دستوری که «امن» حساب میشود یعنی کپیِ پرسشمحور فایلها، نیمی از فایلها را بیصدا رد میکند. این نوشته از همان نقطه شروع میکند: کسی که با دسترسی root روی یک جعبهٔ AlmaLinux با سیپنل و LiteSpeed نشسته و میخواهد فقط یک اکانت را تازه کند.
هر ادعا در ادامه قابل آزمون است. اگر خروجی دستوری با آنچه اینجا نوشته شده فرق کرد، خروجیِ خودِ سرور را ملاک بگیرید، نه این متن را.
پروسههایی که اینجا میبندیم دقیقاً چه هستند
روی نصبهای سیپنل با LiteSpeed، کد PHP داخل پروسهٔ وبسرور اجرا نمیشود. وبسرور برای هر نامکاربری، پروسههای فرزند جداگانهای بالا میآورد که به آنها worker میگوییم؛ هر کدام یک باینری مفسرند که از وبسرور درخواست میگیرند و پاسخ برمیگردانند. دو ویژگیِ این ساختار، کل بحث ما را ممکن میکند.
ویژگی اول: آن پروسهها با نامکاربریِ خودِ اکانت اجرا میشوند، نه با کاربر مشترکِ وبسرور. یعنی فیلتر کردن روی نامکاربری، دقیقاً همان چیزی است که میخواهید: مرزِ امنیتیِ بین اکانتها، مرزِ مدیریتِ پروسهها هم هست.
ویژگی دوم: این پروسهها خدمتِ همیشهبالا نیستند؛ زادهشدنیاند. وبسرور وقتی درخواستی به آن اکانت برسد و پروسهای آماده نباشد، یکی میسازد. پس بستنشان قطعیِ برنامهریزیشده نیست؛ یک چرخهٔ عمرِ طبیعی است. هزینهاش هم روشن است: اولین درخواست بعد از بستن، کمی سرد است و مفسر باید فایلها را بخواند و bytecode را از نو بسازد. در عوض، همان اولین درخواست، کدِ تازه را اجرا میکند.
این را با «ریاستارتِ سرویس» اشتباه نگیرید. سرویس، همان پروسهٔ مادری است که گوشدهندههای پورت را نگه داشته و برای همهٔ اکانتها یکی است. پروسههای PHP، فرزندانیاند که هر کدام به یک کاربر تعلق دارند. بستنِ فرزندان، سرویس را دست نمیزند؛ و همین تفاوت، کل دلیلِ بیقطعیِ این عملیات روی یک سرورِ اشتراکی است.
چرا فهرست پروسهها شما را گمراه میکند
حالا تلهٔ اصلی. این پروسهها آرگومانهای خط فرمان خودشان را بازنویسی میکنند؛ یعنی بردارِ آرگومانها در حافظه عوض میشود تا چیزهایی مثل مسیرِ درخواست یا شمارهٔ سوکت در آنجا نوشته شود. نتیجهٔ ملموسش این است که ps -ef برای همهٔ آنها فقط یک کلمه چاپ میکند: نامِ کوتاهِ پروسه. نه مسیرِ باینری، نه شمارهٔ پیکربندی، نه هیچ مسیری از public_html.
هر عادتِ قدیمی روی همین ستون میشکند. اگر با awk روی ستونِ مسیرِ فرمان فیلتر کنید، خروجی خالی میگیرید. اگر دنبال رشتهٔ ea-php در خطِ پروسه بگردید، باز خالی. اگر بگویید «پس اصلاً workerی در کار نیست»، دقیقاً همان نتیجهٔ اشتباهی را گرفتهاید که ساعتها وقت میخورد؛ پروسهها وجود دارند، سالماند، و با نامکاربریِ درست هم اجرا میشوند. فقط ps نامِ کامل را چاپ نمیکند.
ps -ef | awk '$8 ~ /ea-php/ {print $2, $8}'
ps -ef | awk -v u="<user>" '$1 == u && $8 ~ /opt.cpanel/ {print $2, $8}'
pgrep -u <user> lsphp
pgrep -u <user> lsphp | wc -lدو نکتهٔ ریز در همین بلوک. اول، جستجویِ آزاد روی نامِ پروسه معمولاً خودِ فرمانِ جستجو را هم در نتایج نشان میدهد، پس یک خط شلوغتر از آنچه هست میبینید؛ از pgrep استفاده کنید که خودش را گزارش نمیکند. دوم، pgrep وقتی هیچ تطبیقی پیدا نکند هیچ چاپی ندارد و وضعیتِ خروجش 1 است؛ یعنی برای اینکه بتوانید بگویید «واقعاً پروسهای نیست»، باید همان پرسش را با نامکاربریِ تأییدشدهٔ یک اکانتِ فعالِ دیگر هم مقایسه کنید، نه فقط به یک بار اجرا.
پیدا کردن پروسهها فقط با نامکاربری
ملاکِ مطمئن، نامکاربری است. در سیپنل، نامِ کاربریِ اکانت همان نامِ پوشهٔ خانهٔ آن است و فایلهای public_html هم معمولاً با همان مالکاند. پیش از هر دستورِ بستن، این سه خط را بزنید تا مطمئن شوید نام را درست میپرسید:
id <user>
ls -ld /home/<user>/public_html
stat -c '%U:%G %n' /home/<user>/public_html/index.phpاگر id با پیامِ no such user جواب داد، مشکل از پروسهها نیست؛ شما روی جعبهٔ اشتباهی هستید یا نام را غلط تایپ کردهاید. اگر فایلهای زیر public_html با مالکیتِ root دیده شدند، همان لحظه آنها را با همان الگویِ مالکیتِ فایلهای کناری اصلاح کنید؛ وگرنه workerِ تازه، فایلِ تازه را نمیخواند و شما دوباره نتیجه میگیرید که «هیچچیز عوض نشد».
وقتی نامِ کاربری تأیید شد، فهرستِ پروسهها را با شمارش بگیرید تا ببینید چند تاست. تعدادِ کم یعنی پروسهها در همان لحظه زاده نشدهاند و منتظرِ درخواستاند؛ تعدادِ زیاد یعنی بارِ ورودی جدی است. هر دو حالت طبیعی است و هیچکدام نشانهٔ خرابی نیست. یک شمارشِ ساده هم به شما میگوید که چند پروسه در دامنهٔ اثرِ دستورِ بعدیتاناند؛ و این عددی است که باید قبل از اجرا بدانید.
باینری واقعی را از /proc بخوانید
بستنِ پروسهها نیمهٔ کار است. نیمهٔ مهمتر این است که بفهمید آن اکانت واقعاً با کدام باینری از PHP اجرا میشود؛ چون روی یک جعبهٔ سیپنل، نسخهٔ PHP هر اکانت جداگانه انتخاب شده است. روی یک سرور، یک سایت میتواند مفسر 8.1 و سایت دیگری مفسر 8.4 داشته باشد، و همین باعث میشود تنظیمِ یک فایلِ php.ini برای آنها فرق کند.
ps که نامِ کامل را چاپ نمیکند، پس باید از هسته بپرسید. لینکِ /proc/<pid>/exe به باینریِ واقعی اشاره میکند، مستقل از هر بازنویسیِ آرگومان:
for p in $(pgrep -u <user> lsphp); do ls -l "/proc/$p/exe"; done
ls -l "/proc/$(pgrep -n -u <user> lsphp)/cwd"خطِ اول مسیری شبیه /opt/cpanel/ea-php83/root/usr/bin/lsphp نشان میدهد. این تنها راهی است که بعداً میتوانید مفسرِ درست را مستقیم بپرسید؛ چون php روی PATH شما ممکن است نسخهٔ دیگری باشد و تنظیمی که میخواند هیچ ربطی به صفحهٔ آن اکانت نداشته باشد. خطِ دوم، پوشهٔ کاری را میدهد و أرزانترین راه است برای دیدن اینکه آیا پروسهای که در دست دارید واقعاً به خانهٔ همان اکانت بسته است یا به جایی دیگر.
یک حالت را بشناسید: اگر انتهایِ مسیر برچسبِ deleted دیدید، یعنی بستهٔ PHP روی دیسک عوض یا ارتقا یافته در حالی که آن پروسه هنوز از نسخهٔ قبلی در حافظه اجرا میشود. در آن لحظه، فایلِ روی دیسک و کدِ در حافظه دو چیزِ متفاوتاند؛ و بستنِ همان پروسه، تنها رساندنش به نسخهٔ تازه است.
بعد از شناختِ باینری، تنظیم را از همان نسخه بپرسید، نه از مفسرِ پیشفرضِ شل:
/opt/cpanel/ea-php83/root/usr/bin/php -i | grep -E 'validate_timestamps|revalidate_freq'ریاستارت جراحیشده: فقط پروسههای یک کاربر
دستورِ اصلی، کوتاهترین مسیر بین «کد روی دیسک» و «کد در حافظه» است:
pkill -TERM -u <user> lsphp
sleep 3
pgrep -u <user> lsphpسه چیز را دربارهٔ همین سه خط بدانید.
اول، سیگنال TERM و نه KILL. با TERM هر worker درخواستِ جاریاش را تمام میکند و بعد بیرون میآید؛ کشِ صفحه و فایلهای نیمهنوشته در وضعیتِ بد گیر نمیکنند. KILL پروسه را همان لحظه میبندد و اگر وسطِ نوشتنِ کش یا وسطِ یک درخواستِ سنگین باشد، همان لحظه را خراب میکند. KILL را فقط برای پروسهای نگه دارید که به TERM جواب نمیدهد و چند دقیقه هم از عمرش گذشته است.
دوم، -u تمامِ دامنهٔ اثر است. همین یک پرچم، دستور را از «همه را ببند» به «فقط این اکانت» تبدیل میکند. بدون -u، روی سروری که میزبانِ چند سایت شماست، یک بار زدنش یعنی بستنِ همهٔ پروسههای PHP جعبه؛ و این همان کاری است که روی سرورِ اشتراکی، هزینهاش را مستقیم به همهٔ اکانتهای دیگر سرور منتقل میکند.
سوم، وبسرور خودش جایشان را پر میکند. بعد از pkill، اگر فهرستِ تازه را بگیرید باید شمارههای نو ببینید. اگر شمارهها همان است که قبل بود، یعنی یا نامِ کاربری را غلط پرسیدهاید یا در جعبهٔ دیگری هستید؛ نه اینکه دستور اثر نکرده باشد. تکرارِ همان دستور با پرچمهای بزرگتر، این را حل نمیکند.
اگر از شلِ خودِ اکانت کار میکنید و نه از root، همان دستور با نامِ کاربریِ خودتان هم کار میکند، به شرطی که آن شل اصلاً بتواند این پروسهها را ببیند. در محیطهای محدودشده، نمایِ پروسهها گاهی بسته است؛ در این حالت از دسترسیِ مدیریتیِ سرور اجرا کنید. علامتِ این وضعیت آن است که شمارشِ پروسهها صفر میشود در حالی که سایت روشن است.
دامنهٔ اثر: چه میخواستید، چه دستوری زدید، چه چیزی میشکند
جدولِ زیر همان چیزی است که باید پیش از اجرایِ هر دستور مرور کنید. در ستونِ میانی، <user> را با نامِ کاربریِ اکانت و <pid> را با شمارهٔ پروسه جایگزین کنید.
| آنچه میخواستید | دستور | دامنهٔ اثر | |—|—|—| | تازهکردنِ bytecode یک اکانت | pkill -u <user> lsphp | فقط همان اکانت؛ بیقطعی، بهازای یک درخواستِ سرد | | بستنِ یک پروسهٔ مشکوک | kill <pid> | فقط همان شماره؛ بقیه دستنخورده | | تازهکردنِ کد همهٔ سایتها | pkill lsphp | همهٔ اکانتها؛ روی جعبهٔ اشتراکی کارِ خطرناک | | اعمالِ تنظیمِ تازهٔ وبسرور | systemctl restart lshttpd | همهٔ سایتها، اتصالهای باز، کشِ صفحات | | کنترلِ همان سرویس با اسکریپتِ خودِ وبسرور | lswsctrl restart | سرویس و همهٔ پروسههای PHP؛ همان دامنهٔ ریاستارتِ کامل | | فهمیدنِ وضعیتِ وبسرور | systemctl is-active lshttpd | هیچ؛ فقط میخواند | | دیدنِ پروسه با نامِ فرضی | pidof lsws | هیچ؛ و معمولاً نتیجهٔ غلط میدهد | | گذاشتنِ فایلها سرِ جایشان | rsync -a یا \cp -a | دیسک؛ تا پروسهٔ تازه ساخته نشود دیده نمیشود | | اصلاحِ مالکیتِ فایلها | chown -R <user> <path> | دسترسیها؛ میتواند سایت را از کار بیندازد |
قاعدهٔ فشرده: برای تازهکردنِ کدِ PHP یک اکانت، فقط ردیفِ اول. هر ردیفِ دیگری که برای همان هدف به کار میبرید، دامنهٔ اثرش بزرگتر از خواستهٔ شماست.
نامِ سرویس با نامِ باینری یکی نیست
روی نصبهای استاندارد LiteSpeed، واحدِ سرویس چیزی است و باینریِ اجرایی چیزِ دیگر. سرویس lshttpd است؛ اما خیلیها از روی عادت lsws را میجوند، چون نامِ پوشهٔ نصب همان است. نتیجه: pidof lsws خروجیِ خالی میدهد، و خروجیِ خالی در ذهنِ ما یعنی «وبسرور پایین است». در حالی که وبسرور سالم است، گوش میدهد و پاسخ میدهد؛ فقط شما نام را اشتباه پرسیدهاید.
حدس نزدن، راهِ درست است. مسیرِ اجرایی را از خودِ واحدِ سرویس بخوانید، بعد همان نام را بپرسید:
systemctl cat lshttpd | grep -i '^ExecStart'
systemctl is-active lshttpd
pidof lsws
pidof litespeedخطِ اول، سطرِ ExecStart را چاپ میکند و در آن، نامِ واقعیِ پروسهٔ مادر نوشته شده است؛ دو خطِ آخر را عمداً با دو نامِ متفاوت اجرا کنید تا ببینید کدام خالی برمیگردد. در کنارشان، دو شاهدِ مستقل هم دارید که هیچ ربطی به نامِ پروسه ندارند: پورتهایِ گوشدهنده و یک پاسخِ زنده.
ss -lntp | grep -E ':80 |:443 '
curl -s -o /dev/null -w '%{http_code}\n' http://localhost/این دو خط، سالمبودنِ سرویس را ثابت میکنند حتی وقتی pidof خالی است. عادت کنید که خروجیِ خالیِ یک ابزار، شاهدِ نبودنِ چیزی نیست؛ فقط شاهدِ این است که پرسشِ شما با آن نام، جوابی ندارد. روی سرورِ اشتراکی، همین یک جمله جلوی چند ریاستارتِ بیمورد را میگیرد.
تلهٔ کپیِ پرسشمحور: دیپلویِ نصفهنیمه
پیش از بستنِ پروسهها باید مطمئن شوید فایلها واقعاً جابهجا شدهاند. اینجا یک عادتِ سادهٔ مدیریتی، نصفِ دیپلوی را میسوزاند. در شلِ root روی بسیاری از نصبها، cp به شکلِ پرسشمحور تعریف شده است؛ یعنی یک alias فعال که پیش از بازنویسی هر فایل سؤال میپرسد. اگر همان را درونِ یک حلقه یا پشتِ یک لوله به کار ببرید و ورودیِ استاندارد بسته باشد، سؤال بیجواب میماند، فایل رد میشود، و شما در خروجی هیچ خطایی نمیبینید.
خطرناکبودنش برای دقیقاً همین موضوعِ مقاله روشن است: پوشهای که نیمیاش تازه و نیمیاش قدیمی است، رفتاری نصفه نشان میدهد. شما آن را «کش قاطیکرده» تفسیر میکنید، در حالی که دیسک قاطی کرده است. پس اول تعریفِ فعال را ببینید، و بعد شکلِ صریح و غیرتعاملیِ همان کپی را بزنید؛ باینریِ واقعیِ دستور، نه نامِ مستعارش:
type cp
alias | grep -E "='cp|='mv"
\cp -a /home/<user>/staging-build/public/ /home/<user>/public_html/گزینهٔ -a در کپی، زمانهایِ فایل را هم حفظ میکند. این دومین تلهٔ همین بخش است: اگر برچسبِ زمانیِ فایلِ تازه، قدیمیتر از چیزی باشد که قبلاً روی سرور بوده، مقایسهٔ زمانی، «تازهتر» را نمیبیند و کشِ bytecode دوباره ساخته نمیشود. بستنِ پروسهها آن لحظه مشکل را حل میکند، چون پروسهٔ نو از روی دیسک میسازد؛ ولی در دیپلویِ بعدی همان درد برمیگردد. راهحلِ دائمی، شادابکردنِ زمانها بعد از کپی است:
find /home/<user>/public_html/wp-content -name '*.php' -exec touch {} +
stat -c '%n %y' /home/<user>/public_html/wp-content/index.phpدر پایان، به وضعیتِ خروجِ دستورِ کپی اعتماد نکنید؛ مبدأ و مقصد را با هش مقایسه کنید. sha256sum روی یک فهرستِ فایل، تنها شاهدِ قابلاتکاست که میگوید دیپلوی کامل بوده، نه فقط اجرا شده.
بعد از بستنِ پروسهها: دو چیز را جداگانه تأیید کنید
بستنِ پروسهها ادعایِ شماست؛ دیدنِ رفتارِ تازه، شاهدِ آن. دو پرسشِ جدا وجود دارد و هر کدام ابزارِ خودش را میخواهد.
پرسشِ اول: آیا پروسهها واقعاً نویند؟ فهرست را دوباره بگیرید و شمارهها را با قبل مقایسه کنید؛ زمانِ شروع را هم میتوانید بپرسید:
ps -o pid=,lstart=,user= -p "$(pgrep -u <user> lsphp | paste -sd, -)"اگر شمارهها همان است که قبل بود، هیچ پروسهای بسته نشده و هیچ کشی هم تازه نشده است؛ هر نتیجهگیریِ دیگری از این به بعد بیاساس است.
پرسشِ دوم: آیا رفتارِ تازه سرو میشود، و آیا این پاسخ از کشِ صفحه آمده؟ هدرها را بخوانید:
curl -s -D - -o /dev/null 'https://<site-domain>/' | grep -iE 'cache|age|last-modified'در پاسخِ کششده، برچسبِ hit را در هدرِ کش میبینید؛ یعنی بدنهٔ HTML از کشِ وبسرور آمده و اصلاً PHP اجرا نشده است. در این حالت، عوضنشدنِ رفتار، شاهدی بر سالمبودنِ کشِ bytecode نیست؛ شاهدِ این است که اصلاً به PHP نرسیدهایم. برای بیرونآمدن از این دوبهلویی، یک نشانهٔ مختصر در کدِ تازه بگذارید که فقط در مسیرِ جدید تولید میشود؛ همان نشانه در خروجیِ صفحه، جوابِ قطعی را میدهد.
قاعدهٔ آخرِ این بخش: از یک درخواست نتیجه نگیرید. درخواستِ اول بعد از بستنِ پروسهها میتواند هنوز کشِ قدیمی را برگرداند، چون کشِ صفحه مستقل از کشِ bytecode است و با این عملیات ریشهکن نمیشود. دو درخواستِ مجزا با دو نشانهٔ متفاوت، و مقایسهٔ مقدارِ هدر بین آنها، پاسخ را روشن میکند.
چه زمانی باید تنظیم را عوض کرد، نه پروسه را
اگر برای هر دیپلوی مجبورید پروسهها را ببندید، شما یک مرحلهٔ رسمی در اسکریپتِ استقرار ندارید؛ این ضعفِ فرایند است، نه ضعفِ سرور. در پروداکشنِ پربار میتوان مقایسهٔ زمانی را خاموش نگه داشت، به شرطی که همین بستنِ پروسهها، یک مرحلهٔ اجباری و خودکارِ پایانِ دیپلوی باشد. اگر چنین مرحلهای ندارید، روشنکردنِ مقایسهٔ زمانی با فاصلهٔ بازبینیِ 60 ثانیه، انتخابِ ارزانتر است.
فایلِ تنظیم، مالِ همان نسخهای است که پروسه اجرا میکند، نه همان که در شلِ شماست:
ls /opt/cpanel/ea-php*/root/etc/php.d/10-opcache.iniدو نکتهٔ عملی. یک، تغییرِ php.ini هم تا پروسهٔ تازهای ساخته نشود دیده نمیشود؛ یعنی دستورِ بستن، بخشی از همین اصلاح است نه جایگزینش. دو، روی سیپنل هر نسخهٔ PHP فایلِ خودش را دارد، پس میتوانید برای یک اکانت مقایسهٔ زمانی روشن و برای اکانتِ دیگر خاموش داشته باشید و هیچوقت متوجه نشوید؛ مگر اینکه فهرست بگیرید.
این حلقه، همان چیزی است که سؤالِ «روی این جعبه چه اکانتی با چه نسخهای اجرا میشود؟» را یکجا جواب میدهد:
for d in /home/*/; do
u=$(basename "$d")
p=$(pgrep -u "$u" lsphp | head -1)
[ -n "$p" ] && printf '%s -> %s\n' "$u" "$(readlink -f "/proc/$p/exe")"
doneخروجیِ این حلقه اغلب غافلگیرکننده است: چند اکانت، چند باینریِ متفاوت. از آن به بعد دیگر نمیتوانید بگویید «تنظیمِ سرور» یک مقدار دارد؛ تنظیم، بهازای هر اکانت یک مقدار دارد و هر عملیاتِ تازهسازی هم بهازای هر اکانت یک دستورِ جدا.
اگر دستور اجرا میشود و هیچ اتفاقی نمیافتد
بدترین حالتِ این عملیات، شکستِ بیصداست: دستور اجرا میشود، چیزی چاپ نمیشود، رفتارِ سایت هم عوض نمیشود. تقریباً همیشه یکی از این سه است.
اول، جعبهٔ اشتباه. سه خطِ اولِ هر مجموعهدستور باید هویتِ محیط را ثابت کند: نامِ میزبان، مسیرِ فعلی، و تعدادِ خانهها. اگر اکانتِ هدف در /home نبود، همانجا بایستید. اجازه ندهید شش دستور پشتِ سرِ هم جداگانه شکست بخورد و شما در پایان با شش پیامِ خطایِ نامفهوم روبهرو شوید.
دوم، نامِ کاربریِ اشتباه. اگر نامِ کاربری شناختهشده نباشد، پیامِ invalid user name میگیرید و هیچ پروسهای بسته نمیشود. نام را از getent passwd <user> بگیرید، نه از حافظه؛ و اگر از راهِ دور کار میکنید، همین نام را با فهرستِ اکانتهای پنل هم مقایسه کنید.
سوم، کشِ صفحه. پروسهها را درست بستهاید، کدِ نو هم در حافظه ساخته شده، ولی آنچه کاربر میبیند بدنهٔ ذخیرهشده در کشِ وبسرور یا افزونهٔ کش است. نشانهٔ این حالت، همان برچسبِ hit در هدرهاست که در بخشِ قبل آمد. درمانش هم بستنِ دوبارهٔ پروسهها نیست؛ پاککردنِ همان کشِ صفحه است، و به همین ترتیب.
ترتیبِ نهاییِ کار
ترتیبی که جایِ حدسزدن را میگیرد:
۱. هویتِ محیط را ثابت کنید: نامِ میزبان، مسیرِ فعلی، فهرستِ اکانتها. ۲. نامِ کاربریِ اکانت را تأیید کنید و مالکیتِ فایلهای دیپلویشده را با stat ببینید. ۳. تمامیتِ فایل را با هش مقایسه کنید؛ کپیِ پرسشمحور را با شکلِ صریح و غیرتعاملی جایگزین کنید. ۴. پروسهها را فقط با نامکاربری بشمارید: pgrep -u <user> lsphp؛ برای دیدنِ مسیر به ps اتکا نکنید. ۵. باینریِ واقعی را از /proc/<pid>/exe بخوانید و تنظیم را از همان نسخه بپرسید. ۶. با pkill -TERM -u <user> lsphp فقط همان اکانت را ببندید؛ سرویسِ کلِ وبسرور را نه. ۷. شمارههایِ تازهٔ پروسهها را ببینید؛ نبودنِ شمارهٔ تازه یعنی هیچچیز تازه نشده است. ۸. با دو درخواست و خواندنِ هدرها، هم رفتارِ تازه و هم وضعیتِ کشِ صفحه را جداگانه تأیید کنید. ۹. اگر این کار تکراری است، بستنِ پروسهها را بهعنوانِ مرحلهٔ پایانیِ اسکریپتِ استقرار بنویسید.
این چند گام، تفاوتِ بین «دوباره هیچچیز عوض نشد» و دو دقیقه کارِ مشخص است. چیزی که در این مسیر ارزشِ وقتگذاشتن دارد، خودِ دستور نیست؛ فهمیدنِ همان چیزی است که ابزارها دربارهٔ پروسهها چاپ نمیکنند، و عادتِ اینکه خروجیِ خالی را نشانهٔ نبودنِ چیزی نگیرید.