روی هاست اشتراکی ایرانی، لاراول اجرا می‌شود. ولی نه با همان فرضیاتی که در مستندات رسمی و آموزش‌های خارجی می‌بینید. آن مستندات یک سرور مجازی را فرض می‌گیرند: کاربر root، اجازه‌ی نصب هر باینری، یک سرویس‌مدیر که پروسه‌های دائمی را زنده نگه می‌دارد، و یک ریشه‌ی وب که خودتان تعیینش می‌کنید. روی سی‌پنل اشتراکی هیچ‌کدام از این‌ها را ندارید.

این نوشته چک‌لیست چیزهایی است که سر راه‌اندازی همین فروشگاه روی یک هاست اشتراکی ایرانی یاد گرفتیم. بعضی‌شان را می‌شد قبل از خرید پرسید و بعضی‌ها فقط بعد از برخورد با دیوار معلوم شدند.

۱. نسخه‌ی PHP و اینکه خط فرمان با وب یکی نیست#

اولین چیزی که باید چک کنید بدیهی به نظر می‌رسد: هاست چه نسخه‌هایی از PHP دارد و آیا خودتان می‌توانید عوضش کنید. تقریباً همه‌ی هاست‌های سی‌پنلی ابزار MultiPHP یا «تغییر نسخه PHP» دارند.

ولی نکته‌ی مهم جای دیگری است: نسخه‌ای که برای وب انتخاب می‌کنید لزوماً همان نسخه‌ای نیست که در ترمینال اجرا می‌شود. روی سرور ما وب روی PHP 8.4 بود ولی دستور php در شل نسخه‌ی ۸.۱ سیستم را صدا می‌زد. یعنی php artisan migrate با یک مفسر دیگر اجرا می‌شد و می‌توانست روی نحو جدیدتر زبان بشکند.

با این دو دستور تفاوت را ببینید:

Bash
php -v
php84 -v

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

Bash
ls -d /opt/cpanel/ea-php*/root/usr/bin/php

این موضوع یک دنباله‌ی خطرناک دارد که در تله‌ی php84 در کرون‌جاب نوشتیم: چیزی که در ترمینال کار می‌کند ممکن است در کرون اصلاً پیدا نشود.

۲. فهرست disable_functions؛ مهم‌ترین چیزی که کسی نمی‌پرسد#

هر هاست اشتراکی برای امنیت فهرستی از توابع PHP را غیرفعال می‌کند. این فهرست تعیین می‌کند کدام راه‌حل‌ها برای شما اصلاً وجود ندارند — نه اینکه توصیه نمی‌شوند، وجود ندارند.

با این دستور ببینیدش:

Bash
php84 -r 'echo ini_get("disable_functions"), PHP_EOL;'

اگر exec و shell_exec و system و proc_open در فهرست باشند، یعنی هیچ‌وقت نمی‌توانید از دل PHP به یک باینری خارجی شل بزنید. هر آموزشی که می‌گوید «کافی است ImageMagick را صدا بزنید» یا «با ffmpeg ویدیو را کوچک کنید» برای شما بی‌فایده است.

اگر symlink هم بسته باشد ماجرا جدی‌تر می‌شود؛ جزئیاتش این‌جاست.

۳. دسترسی SSH یا ترمینال سی‌پنل#

بعضی پلن‌های اشتراکی SSH ندارند و بعضی دارند ولی باید فعالش کنید. سی‌پنل جدید یک ابزار Terminal هم دارد که همان شل را در مرورگر باز می‌کند و برای کار ما کافی است.

بدون شل، استقرار لاراول یعنی آپلود دستی فایل‌ها با FTP — یعنی هر بار composer install را روی سیستم خودتان بزنید و پوشه‌ی vendor را با چند هزار فایل آپلود کنید. شدنی است ولی دردناک، و هر به‌روزرسانی همین چرخه را تکرار می‌کند.

پس این را قبل از خرید بپرسید. اگر فروشنده جواب روشنی نداد، احتمالاً یعنی نه.

۴. کرون: فاصله‌ی حداقل و شل غیرتعاملی#

لاراول برای زمان‌بندی به یک کرون هر دقیقه‌ای نیاز دارد:

Code
* * * * * cd /home/USER/app && /opt/cpanel/ea-php84/root/usr/bin/php artisan schedule:run >> /dev/null 2>&1

دو چیز را چک کنید:

  • حداقل فاصله‌ی مجاز. بعضی هاست‌ها اجازه‌ی کرون هر دقیقه نمی‌دهند و حداقل را روی پنج یا پانزده دقیقه می‌گذارند. این یعنی schedule:run استاندارد لاراول دقیق کار نمی‌کند و باید زمان‌بندی‌تان را با فاصله‌ی واقعی هماهنگ کنید.
  • مسیر مطلق مفسر. در کرون هیچ فایل ~/.bashrc خوانده نمی‌شود، پس نام‌های میان‌بری که در ترمینال جواب می‌دهند این‌جا «command not found» می‌گیرند.

۵. ریشه‌ی وب و جای پوشه‌ی پروژه#

روی سی‌پنل ریشه‌ی وبِ دامنه‌ی اصلی public_html است و معمولاً نمی‌توانید تغییرش دهید. لاراول اما می‌خواهد فقط پوشه‌ی public در دسترس وب باشد و بقیه‌ی پروژه بیرون بماند.

سه راه وجود دارد و فقط یکی‌شان بی‌دردسر است:

راهنتیجه
کل پروژه داخل public_htmlفایل env و پوشه‌ی storage از وب قابل دسترس‌اند. هرگز
پروژه بیرون، public_html سیملینک به publicبهتر، ولی به تابع symlink نیاز دارد که ممکن است بسته باشد
پروژه بیرون، index.php دستی در public_htmlکار می‌کند و به هیچ تابع بسته‌ای نیاز ندارد

راه سوم یعنی محتوای public را در public_html بگذارید و در index.php مسیرها را یک پله بالاتر اصلاح کنید. ساختاری که ما داریم همین است: پروژه در ~/app و ریشه‌ی وب ~/public_html که فقط ورودی و دارایی‌های عمومی را دارد. جزئیات کامل این ساختار و تله‌ی سیملینک در نوشته‌ی storage:link آمده.

۶. محدودیت‌های MySQL که فقط خواندنی‌اند#

روی هاست اشتراکی به متغیرهای سراسری MySQL دسترسی ندارید. یعنی هر راه‌حلی که با «این مقدار را در فایل تنظیمات MySQL عوض کن» شروع شود برای شما بسته است. سه موردی که بیشتر از همه به آن می‌خورید:

  • سقف طول ایندکس. روی جدول‌های utf8mb4 هر کاراکتر تا چهار بایت حساب می‌شود و ایندکس روی رشته‌های بلند از سقف رد می‌شود. نتیجه‌اش خطای ۱۰۷۱ است؛ راه‌حل کاملش این‌جاست.
  • مقدار sql_mode. اگر STRICT_TRANS_TABLES روشن باشد، داده‌ی بلندتر از ستون به‌جای بریده‌شدن خطا می‌دهد. این خبر خوبی است نه بد — چرا.
  • مقدار innodb_ft_min_token_size. اگر روی سه باشد و نتوانید عوضش کنید، جستجوی FULLTEXT کلمات دوحرفی فارسی را برای همیشه نادیده می‌گیرد.

با یک کوئری ساده وضعیتشان را ببینید:

SQL
SHOW VARIABLES LIKE 'sql_mode';
SHOW VARIABLES LIKE 'innodb_ft_min_token_size';
SHOW VARIABLES LIKE 'max_allowed_packet';

۷. حافظه، زمان اجرا و شمارنده‌هایی که کسی به شما نمی‌گوید#

چهار عدد را از پنل هاست یا از پشتیبانی بگیرید:

  • memory_limit — برای پردازش تصویر حیاتی است. یک عکس دوازده مگاپیکسلی هنگام باز شدن با GD چندین برابر حجم فایلش حافظه می‌گیرد، چون به آرایه‌ی پیکسل تبدیل می‌شود نه به همان بایت‌های فشرده‌ی JPEG.
  • max_execution_time — سقف زمان هر درخواست وب.
  • upload_max_filesize و post_max_size — این دو باید با هم هماهنگ باشند وگرنه آپلود بی‌صدا شکست می‌خورد.
  • Entry Processes — تعداد درخواست‌های همزمانی که حساب شما اجازه دارد. وقتی پر شود بازدیدکننده خطای ۵۰۸ می‌گیرد نه ۵۰۰، و در لاگ لاراول هیچ ردی از آن نیست.

چیزهایی که روی هاست اشتراکی ذاتاً ندارید#

این‌ها را هیچ ترفندی برنمی‌گرداند. بهتر است از اول بدانید تا معماری‌تان را روی چیزی نسازید که وجود ندارد:

  • پروسه‌ی دائمی. خبری از یک queue:work همیشه‌روشن نیست. جایگزین عملی: درایور database برای صف و یک کرون که queue:work --stop-when-empty را می‌زند.
  • وب‌سوکت. پورت دلخواه باز نمی‌شود.
  • Redis و Memcached معمولاً نیستند. کش را روی file یا database بگذارید.
  • نصب اکستنشن دلخواه PHP. فقط از فهرست آماده‌ی هاست می‌توانید انتخاب کنید.
  • باینری خارجی. نه ImageMagick، نه ffmpeg، نه wkhtmltopdf.

پس چه چیزی می‌ماند؟#

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

  • پردازش تصویر با GD خام به‌جای ImageMagick (چطور)
  • جستجو با ستون نرمال‌شده به‌جای FULLTEXT (چرا)
  • کار سنگین در کرون شبانه به‌جای صف زنده
  • کش فایل به‌جای Redis

و وقتی چیزی خراب شد و خطا مبهم بود، اول این نوشته درباره‌ی APP_DEBUG را بخوانید؛ روی چنین سروری حالت دیباگ می‌تواند علت واقعی را پنهان کند نه آشکار.

۸. مهاجرت‌ها و اینکه چه چیزی نباید خودکار باشد#

یک تصمیم که زود بگیرید بهتر است: آیا استقرار خودکار باید مهاجرت دیتابیس را هم اجرا کند؟

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

اسکریپت استقرار ما فقط سه کار می‌کند:

Bash
git pull --ff-only
php84 artisan optimize:clear
php84 artisan settings:sync

و مهاجرت دستی و آگاهانه اجرا می‌شود:

Bash
php84 artisan migrate --force

سوییچ --force لازم است چون محیط production است و لاراول بدون آن تأیید تعاملی می‌خواهد که در اسکریپت جواب نمی‌دهد.

نکته‌ی مهم دیگر: git pull --ff-only عمدی است. اگر روی سرور کسی فایلی را دستی عوض کرده باشد، این دستور شکست می‌خورد به‌جای اینکه ادغام کند. شکست صریح خیلی بهتر از یک ادغام غافلگیرکننده روی سایت زنده است.

۹. بک‌آپ: چیزی که هاست می‌دهد کافی نیست#

تقریباً همه‌ی هاست‌ها بک‌آپ دوره‌ای دارند. دو سؤال را قبل از تکیه‌کردن به آن بپرسید:

  • هر چند وقت و چند نسخه نگه می‌دارد؟ بک‌آپ هفتگی یعنی تا شش روز داده می‌تواند از بین برود.
  • خودتان می‌توانید بازیابی کنید یا باید تیکت بزنید؟ فاصله‌ی این دو در یک بحران واقعی، ساعت‌هاست.

بک‌آپ مستقل خودتان را هم داشته باشید. چون از دل PHP نمی‌توانید mysqldump را صدا بزنید، آن را در کرون بگذارید که شل کامل دارد:

Bash
0 3 * * * mysqldump -u USER -pPASS DBNAME | gzip > /home/USER/backups/db-$(date +\%F).sql.gz

سه نکته:

  • در کرون علامت درصد معنای خاص دارد و باید با بک‌اسلش خنثی شود، وگرنه دستور نصفه اجرا می‌شود.
  • پوشه‌ی بک‌آپ نباید داخل ریشه‌ی وب باشد. یک فایل SQL قابل دانلود یعنی کل دیتابیس شما در دسترس عموم.
  • نسخه‌های قدیمی را پاک کنید وگرنه فضای حساب پر می‌شود.

اگر مقدارها را از فایل .env می‌خوانید، حواستان به تله‌ی پایان‌خط ویندوزی باشد؛ دقیقاً همین اسکریپت بود که ما را به آن باگ رساند.

۱۰. چیزی که آخر از همه می‌فهمید: فایروال#

خیلی از هاست‌های ایرانی یک لایه‌ی محافظت در برابر ربات دارند. برای بازدیدکننده‌ی عادی نامرئی است، ولی روی این چیزها اثر می‌گذارد:

  • درخواست‌های خودکار به پنل مدیریت ممکن است چالش کپچا بگیرند.
  • ابزارهای بررسی سایت ممکن است بلاک شوند و گزارش اشتباه بدهند.
  • درخواست‌های پشت سر هم از یک آی‌پی می‌تواند موقتاً بسته شود، حتی اگر خود شما باشید.

اگر قرار است چیزی را خودکار کنید — انتشار محتوا، همگام‌سازی، مانیتورینگ — این را از اول بدانید. راه‌حل معمولاً این است که کار را از سمت سرور انجام دهید نه از راه HTTP.

قبل از خرید، سه دستور تست#

اگر هاست دوره‌ی بازگشت وجه دارد، ارزان‌ترین پلن را بخرید و قبل از انتقال پروژه‌ی اصلی این سه دستور را بزنید:

Bash
php84 -v
php84 -r 'echo ini_get("disable_functions"), PHP_EOL;'
php84 -r 'echo ini_get("memory_limit"), PHP_EOL;'

اگر نسخه‌ی PHP قابل قبول بود، فهرست توابع بسته کوتاه بود و حافظه از ۲۵۶ مگابایت کمتر نبود، بقیه‌اش قابل مدیریت است. باقی مشکلات را می‌شود دور زد؛ این سه تا را نه.