پروژه را روی هاست اشتراکی جدید آپلود کردیم. فایلها همان فایلهای لوکال بودند، دیتابیس ایمپورت شده بود و .env هم با مقدارهای درست پر شده بود. دامنه را باز کردیم و بهجای صفحهی اول سایت، فهرست پوشهها را دیدیم: app، bootstrap، config، vendor و پایینتر public. این اولین برخورد ما با مسئلهی همیشگیِ لاراول در public_html بود: ریشهی دامنه روی سیپنل پوشهی public_html است، ولی ریشهی وبِ لاراول باید پوشهی public باشد.
اولین واکنش این بود که خب، پس آدرس درست example.com/public است. زدیم و سایت واقعاً بالا آمد. یک لحظه خوشحال شدیم و بلافاصله فهمیدیم چه اتفاق بدی افتاده. اگر public از بیرون دیده میشود، یعنی خود پروژه هم دیده میشود. آدرس example.com/.env را در مرورگر زدیم و مرورگر فایل را دانلود کرد. رمز دیتابیس، APP_KEY، کلید درگاه پرداخت، همه در یک فایل متنی ساده.
این یادداشت همان مسیر است: چرا این تناقض اصلاً وجود دارد، دو راهحل رایجی که مردم انتخاب میکنند و هر دو بد هستند، راه درست و جایگزینش وقتی هاست اجازه نمیدهد، فایل .htaccess استاندارد لاراول خط به خط، و آن دسته ۴۰۴هایی که هیچ ربطی به کد شما ندارند.
چرا لاراول با ریشهی public_html جور در نمیآید#
لاراول یک «کنترلر جلویی» دارد: فایل public/index.php. هر درخواستی که به سایت میرسد باید به همین یک فایل برسد و بقیهی کارها داخل PHP انجام شود. برای همین طراحی، لاراول پوشههایش را به دو دسته تقسیم کرده است.
آنچه باید از وب دیده شود، فقط داخل public است: index.php، فایلهای ساختهشدهی CSS و JS، تصویرها، favicon و robots.txt.
آنچه هرگز نباید از وب قابل دسترس باشد، بقیهی پروژه است:
- فایل
.envکه رمز دیتابیس،APP_KEYو کلید همهی سرویسهای بیرونی داخلش است. - پوشهی
storageکه لاگها، کش، فایلهای سشن و فایلهای آپلودی خصوصی را نگه میدارد. یکstorage/logs/laravel.logمعمولی میتواند مسیر کامل فایلها، بخشی از کوئریها و حتی مقدار متغیرها را لو بدهد. - پوشهی
vendorبا کل کد پکیجها؛ نسخهی دقیق هر پکیج از روی همین پوشه خوانده میشود و کار مهاجم را برای پیدا کردن آسیبپذیریِ شناختهشده ساده میکند. - پوشهی
databaseبا مهاجرتها و سیدرها، که ساختار جدولها و گاهی دادهی نمونه را نشان میدهد. - پوشهی
.gitاگر پروژه را با گیت منتقل کرده باشید. کل تاریخچهی کد از همینجا قابل بازسازی است.
حالا طرف مقابل: روی هاست اشتراکی با سیپنل، دامنهی اصلی بهطور پیشفرض به پوشهی ~/public_html وصل است. هر چیزی که داخل آن بگذارید از وب قابل دسترس است، مگر اینکه صریحاً جلویش را بگیرید. همینجا تناقض شکل میگیرد: هاست میگوید ریشه public_html است، لاراول میگوید ریشه باید public باشد.
کل بحث این مقاله یک جمله است: بهجای اینکه لاراول را به شکل هاست دربیاورید، ریشهی هاست را به پوشهی درست اشاره بدهید.
راه بد اول: ریختن کل پروژه در public_html#
رایجترین کاری که انجام میشود این است: فایل زیپ پروژه را داخل public_html باز میکنند و بعد یا آدرس را با /public میدهند، یا یک index.php سرِ هم میکنند که درخواست را به public/index.php بفرستد.
سایت کار میکند. مشکل جای دیگری است. یک دستور ساده کافی است تا ببینید چه چیزی بیرون افتاده:
curl -s https://example.com/.env | head -n 8اگر خروجی چیزی شبیه این باشد، پرونده همانجا بسته است:
APP_NAME=Debug
APP_ENV=production
APP_KEY=base64:0m3Zx8Qk1sT7vB2nR4pW9yL6cD5hJ0aE8fG3uI1oK2s=
APP_DEBUG=false
DB_DATABASE=user_shop
DB_USERNAME=user_shopadmin
DB_PASSWORD=***و این تنها فایل نیست. اینها هم معمولاً باز هستند:
curl -sI https://example.com/storage/logs/laravel.log | head -n 1
curl -sI https://example.com/.git/config | head -n 1
curl -sI https://example.com/composer.lock | head -n 1با APP_KEY میشود کوکی سشن را جعل کرد. با رمز دیتابیس، اگر هاست اجازهی اتصال از بیرون بدهد، مستقیم میشود وارد دیتابیس شد. با composer.lock نسخهی دقیق هر پکیج معلوم است. و لاگ لاراول، مخصوصاً وقتی حالت اشکالزدایی روشن مانده باشد، مسیر فایلها و بخشی از داده را نشان میدهد؛ دلیل اینکه چرا APP_DEBUG روی هاست اشتراکی باید خاموش باشد هم دقیقاً همین است.
جملهای که همیشه در دفاع از این روش میشنویم این است که «کسی که آدرس فایل را نمیداند». اسکنرهای خودکار همین چند مسیر ثابت را روی همهی دامنهها امتحان میکنند. اگر لاگ دسترسی هاستتان را باز کنید، درخواست به /.env را احتمالاً از همین هفتهی اول پیدا میکنید.
راه بد دوم: دستکاری index.php و ماندن اپلیکیشن زیر ریشه#
راه دوم کمی هوشمندانهتر به نظر میرسد و برای همین خطرناکتر است. محتوای پوشهی public را داخل public_html میریزند، بقیهی پروژه را در public_html/laravel میگذارند و بعد مسیرهای index.php را عوض میکنند:
require __DIR__.'/laravel/vendor/autoload.php';
$app = require_once __DIR__.'/laravel/bootstrap/app.php';ظاهر کار تمیز است: آدرس سایت درست شده و /public از URL حذف شده. ولی پوشهی اپلیکیشن هنوز زیر ریشهی وب است. یعنی این آدرس هنوز جواب میدهد:
curl -s https://example.com/laravel/.env | head -n 3از اینجا به بعد معمولاً وارد یک مسابقهی بیپایان میشوند: با .htaccess جلوی پوشهی laravel را میبندند، بعد یادشان میرود روی زیرشاخهها هم ببندند، بعد یک قانون بازنویسیِ دیگر ترتیب را به هم میزند. یک تنظیم اشتباه در یک فایل، همهی محافظت را برمیدارد و هیچ خطایی هم نمیدهد؛ فقط بیصدا باز میشود.
سه ایراد دیگر هم دارد که در عمل بیشتر آزار میدهد:
- فایل
public/index.phpجزو فایلهای خود فریمورک است. با هر ارتقای لاراول یا هرgit pullدوباره جایگزین میشود و باید هر بار دستی ویرایشش کنید. یک بار فراموش کنید، سایت با خطای «کلاس پیدا نشد» میخوابد. - دستور
php artisanاز ریشهی پروژه اجرا میشود، نه از ریشهی وب. با این چیدمان دو مفهوم «ریشه» با هم قاطی میشوند و دستورهای کش و مهاجرت از مسیر اشتباه اجرا میشوند. - تابع
public_path()همچنان پوشهیpublicداخل پروژه را برمیگرداند، نهpublic_htmlرا. یعنی هر پکیجی که فایلی در ریشهی وب مینویسد، آن را جایی مینویسد که وبسرور نمیبیند.storage:linkهم دقیقاً به همین دلیل لینک را در جای بیمصرف میسازد.
خلاصه: این روش امنیت را به یک فایل تنظیمات شکننده گره میزند و نگهداری را برای همیشه سنگین میکند.
راه درست: اپلیکیشن بیرون از public_html و ریشه روی پوشهی public#
راه درست ساده است. کل پروژه بیرون از ریشهی وب میماند و فقط ریشهی دامنه را به پوشهی public همان پروژه اشاره میدهید.
- پروژه را در پوشهای بیرون از
public_htmlبگذارید، مثلاً~/app:
cd ~
mkdir -p app
# فایلهای پروژه را داخل ~/app بگذارید
ls ~/app/public/index.php- در سیپنل به بخش Domains بروید. کنار دامنه گزینهی مدیریت یا ویرایش را باز کنید و مقدار Document Root را از
public_htmlبهapp/publicتغییر بدهید. مسیر نسبت به پوشهی خانگی حساب میشود، پس همانapp/publicکافی است.
بسته به نسخهی سیپنل، این گزینه ممکن است زیر Domains، Addon Domains یا Subdomains باشد. روی بعضی هاستها ریشهی دامنهی اصلی قفل است و فقط دامنههای اضافه و زیردامنهها قابل تغییرند؛ در آن حالت سراغ بخش بعدی بروید یا از پشتیبانی بخواهید ریشه را جابهجا کند. این کار برای پشتیبانی یک تغییر یکخطی است.
- مجوزها را چک کنید. پوشهها
755و فایلها644کافی است. پوشههایstorageوbootstrap/cacheباید برای کاربر PHP قابل نوشتن باشند:
find ~/app -type d -exec chmod 755 {} \;
find ~/app -type f -exec chmod 644 {} \;
chmod -R 775 ~/app/storage ~/app/bootstrap/cacheروی هاست اشتراکی معمولاً PHP با همان کاربر سیپنل اجرا میشود، پس 755 هم برای storage کار میکند و نیازی به 777 نیست. 777 را روی هیچ پوشهای نگذارید.
- لینک پوشهی فایلهای عمومی را بسازید. جزئیات این کار روی سیپنل و تلههای مسیر مطلق را در یادداشت storage:link روی سیپنل نوشتهایم:
cd ~/app && php artisan storage:link- بعد از جابهجایی ریشه، پوشهی
public_htmlقدیمی دیگر بیمصرف است. خالیاش کنید یا اسمش را عوض کنید تا اشتباهی چیزی از آن سرو نشود. اگر گواهی SSL خودکار هاست برای اعتبارسنجی بهpublic_htmlنیاز دارد، پوشه را نگه دارید ولی خالی بماند.
اگر تازه در حال بالا آوردن پروژه روی هاست اشتراکی هستید، بقیهی کارهای لازم را در چکلیست هاست اشتراکی برای لاراول جمع کردهایم.
وقتی تغییر ریشهی دامنه ممکن نیست: راه سیمبولیکلینک#
بعضی هاستها اجازهی تغییر Document Root دامنهی اصلی را نمیدهند. در این حالت میشود public_html را با یک سیمبولیکلینک به ~/app/public وصل کرد:
mv ~/public_html ~/public_html.bak
ln -s ~/app/public ~/public_html
ls -l ~ | grep public_htmlخروجی باید نشان بدهد که public_html یک لینک است و به app/public اشاره میکند.
سه هشدار که باید قبل از این کار بدانید:
- آپاچی باید اجازهی دنبال کردن لینک را داشته باشد. روی بیشتر هاستهای سیپنل
Options +SymLinksIfOwnerMatchفعال است و چون هر دو مسیر مالک یکسان دارند مشکلی پیش نمیآید. اگر پیکربندی سرور سختگیرتر باشد، نتیجه خطای ۴۰۳ است. - بعضی ابزارهای سیپنل، مثل نصبکنندههای خودکار یا ابزار گواهی SSL، ممکن است
public_htmlرا دوباره به شکل پوشه بسازند و لینک را از بین ببرند. بعد از هر کار مدیریتی در پنل، یک بار وضعیت لینک را چک کنید. - پشتیبانگیری خودکار هاست ممکن است لینک را دنبال نکند یا برعکس، فایلها را دو بار بردارد. یک بار خروجی بکآپ را نگاه کنید.
اگر نه تغییر ریشه ممکن بود و نه سیمبولیکلینک، آخرین گزینه این است: محتوای public را در public_html بگذارید ولی بقیهی پروژه را بیرون از ریشه نگه دارید و مسیرهای index.php را به بیرون اشاره بدهید. این با راه بد دوم یک تفاوت اساسی دارد: در آن روش پوشهی اپلیکیشن زیر ریشه میماند، اینجا نمیماند. باز هم بدهیاش را میپردازید — با هر ارتقا باید index.php را دوباره ویرایش کنید — ولی دستکم .env از وب دیده نمیشود.
فایل htaccess استاندارد لاراول، خط به خط#
فایل public/.htaccess که با خود لاراول میآید تقریباً همیشه همین است:
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>حالا خط به خط:
<IfModule mod_rewrite.c> — همهی محتوای فایل داخل این شرط است. اگر ماژول بازنویسی روی سرور فعال نباشد، آپاچی بهجای خطا دادن، کل بلوک را نادیده میگیرد. این «مهربانی» دقیقاً منبع بخش بزرگی از ۴۰۴های عجیب است، چون هیچ نشانهای باقی نمیگذارد.
Options -MultiViews -Indexes — دو کار جدا. -MultiViews مذاکرهی محتوا را خاموش میکند؛ وگرنه درخواست /report ممکن است خودش به report.php یا report.html وصل شود و روت لاراول هیچوقت اجرا نشود. -Indexes جلوی نمایش فهرست فایلهای یک پوشه را میگیرد.
RewriteEngine On — موتور بازنویسی را روشن میکند. بدون این خط بقیهی قانونها اجرا نمیشوند.
قانون Authorization — بعضی پیکربندیها هدر Authorization را به PHP نمیرسانند. این دو خط میگویند اگر هدر Authorization مقداری دارد، همان مقدار را در متغیر محیطی HTTP_AUTHORIZATION بگذار. اگر ورود با توکن یا Bearer دارید و همهجا کاربر را «احراز نشده» میبینید، نبودن همین دو خط اولین چیزی است که باید چک کنید.
قانون اسلش پایانی — این سه خط اسلش انتهایی را حذف میکنند، نه اضافه. شرط اول میگوید فقط وقتی که مسیر درخواستی یک پوشهی واقعی نیست. شرط دوم آدرسهایی را میگیرد که به / ختم میشوند و بخش قبل از آن را در %1 نگه میدارد. قانون هم با R=301 به همان آدرس بدون اسلش هدایت دائمی میدهد. نتیجه این است که /blog/post/ و /blog/post دو آدرس جدا برای گوگل نباشند.
قانون نهایی — قلب ماجرا. اگر مسیر درخواستی یک پوشهی واقعی نبود (!-d) و یک فایل واقعی هم نبود (!-f)، درخواست را به index.php بفرست. پرچم [L] یعنی همینجا تمام. توجه کنید که مسیر اصلی در URL دستنخورده باقی میماند؛ لاراول آن را از REQUEST_URI میخواند، نه از پارامتر بازنویسی.
ترتیب همین دو شرط منفی، مهمترین نکتهی این فایل است: فایل و پوشهی واقعی همیشه بر روت لاراول اولویت دارند. این دقیقاً سرچشمهی یکی از ۴۰۴هایی است که در بخش بعد میآید.
۴۰۴های عجیب و ریشههایشان#
قبل از هر چیز یک تفکیک که نصف کار را جلو میبرد: ۴۰۴ را چه کسی ساخته است؟
- اگر صفحهی خطا ظاهر سادهی آپاچی دارد و متنی شبیه «Not Found — The requested URL was not found on this server» نشان میدهد، درخواست اصلاً به PHP نرسیده است. مقصر در لایهی وبسرور است.
- اگر صفحهی ۴۰۴ خود لاراول است (یا قالب
errors/404.blade.phpشماست)، درخواست رسیده و لاراول روتی برایش پیدا نکرده. مقصر در لایهی برنامه است.
پنج ریشهی رایج برای ۴۰۴ سمت وبسرور:
۱. نبودن AllowOverride. اگر در پیکربندی آپاچی برای این مسیر AllowOverride None باشد، فایل .htaccess اصلاً خوانده نمیشود. علامت مشخصش این است که صفحهی اول سایت درست بالا میآید (چون DirectoryIndex خودش index.php را برمیدارد) ولی هر آدرس دیگری ۴۰۴ میدهد. تست قطعیاش هم ساده است: یک خط بیمعنی به اول .htaccess اضافه کنید:
ThisIsNotADirectiveحالا سایت را باز کنید. اگر خطای ۵۰۰ گرفتید، یعنی فایل خوانده میشود و AllowOverride مشکلی ندارد. اگر سایت بدون تغییر کار کرد، یعنی فایل اصلاً خوانده نمیشود. خط را بعد از تست حتماً پاک کنید.
۲. خاموش بودن mod_rewrite. همانطور که گفتیم، <IfModule> این حالت را بیصدا رد میکند. علامتش با حالت قبل یکی است. برای تشخیص، شرط <IfModule mod_rewrite.c> و خط پایانی </IfModule> را موقتاً بردارید. اگر ماژول واقعاً نباشد، آپاچی روی RewriteEngine On خطای ۵۰۰ میدهد و همان خطا جواب شماست. روی هاست اشتراکی این ماژول تقریباً همیشه فعال است؛ اگر نبود، کاری از دست شما برنمیآید جز تیکت زدن.
۳. فایل .htaccess که اصلاً آپلود نشده. چون اسمش با نقطه شروع میشود، فایل مخفی است. کلاینت FTP بهطور پیشفرض فایلهای مخفی را نشان نمیدهد و آپلود هم نمیکند. در فایلمنیجر سیپنل هم باید از تنظیمات، گزینهی نمایش فایلهای مخفی را روشن کنید تا دیده شود. چک کردنش یک دستور است:
ls -la ~/app/public/ | grep htaccessاگر خالی برگشت، فایل نیست. همین.
۴. حساسیت به بزرگی و کوچکی حروف. لوکال شما احتمالاً ویندوز یا مک است و در هر دو، Logo.png و logo.png یک فایلاند. لینوکس اینطور نیست. فایلی که در قالب با نام logo.png صدا زدهاید ولی روی دیسک Logo.png است، روی هاست ۴۰۴ میدهد. همین قاعده برای نام فایل ویوها هم صادق است: resources/views/Home.blade.php با view('home') روی لوکال کار میکند و روی هاست خطای «view not found» میدهد. برای پیدا کردن موارد مشکوک:
find ~/app/public ~/app/resources -name '*[A-Z]*' -type f۵. تصادم مسیر با یک فایل یا پوشهی واقعی. یادتان هست که قانون آخر گفت «اگر فایل یا پوشهی واقعی نبود». اگر روتی به نام /build تعریف کردهاید و در public هم پوشهی build وجود دارد (خروجی ساخت داراییها)، آپاچی به لاراول کاری ندارد و خودش پوشه را سرو میکند. چون -Indexes فعال است و پوشه فایل index ندارد، نتیجه ۴۰۳ آپاچی است، نه اجرای روت شما. همین اتفاق برای /storage میافتد وقتی لینک public/storage شکسته باشد یا اصلاً ساخته نشده باشد. نام روتها را با محتوای پوشهی public مقایسه کنید:
ls ~/app/public
php artisan route:list --columns=uriمحافظت اضافه: فایلهای مخفی و فهرست پوشهها#
حتی وقتی چیدمان درست است و فقط public زیر ریشه قرار دارد، چند خط اضافه در .htaccess ارزش دارد. مثلاً فایل .htaccess خودش، یا فایلهای مخفیای که ابزارهای دیگر جا میگذارند.
Options -Indexes
<FilesMatch "^\.">
Require all denied
</FilesMatch>Options -Indexes جلوی نمایش فهرست فایلها را در پوشههای بدون index میگیرد. الگوی ^\. هر فایلی را که نامش با نقطه شروع شود میبندد؛ یعنی .env و .htaccess و مشابههایشان.
ولی حواستان باشد FilesMatch فقط با نام فایل کار دارد، نه با مسیر. یعنی این بلوک جلوی /.git/config را نمیگیرد، چون نام آن فایل config است و با نقطه شروع نمیشود. برای پوشههای نقطهدار به یک قانون مسیرمحور نیاز دارید:
RedirectMatch 404 /\.(git|svn|env)نکتهی نسخه: Require all denied نحو آپاچی ۲.۴ است. روی سرورهای قدیمیتر با آپاچی ۲.۲ باید Deny from all بنویسید. بیشتر هاستهای امروز ۲.۴ هستند، ولی اگر بعد از افزودن این بلوک خطای ۵۰۰ گرفتید، اولین چیزی که باید عوض کنید همین است.
اگر خواستید پسوندهای خاصی را هم ببندید، حواستان به عوارض جانبی باشد:
<FilesMatch "\.(env|log|sql|sh|bak|lock|yml|yaml)$">
Require all denied
</FilesMatch>اضافه کردن json به این فهرست وسوسهبرانگیز است ولی خطرناک: اگر سایت شما site.webmanifest، manifest.json یا هر فایل JSON دیگری را از سمت مرورگر میخواند، آن هم بسته میشود و بیسروصدا خراب میشود.
چرا htaccess نباید تنها ستون امنیت باشد#
فایل .htaccess یک لایهی راحت است، نه یک مرز. سه دلیل روشن:
- فقط وقتی کار میکند که وبسرور آپاچی باشد و
AllowOverrideاجازه بدهد. اگر روزی سایت پشتLiteSpeedبا پیکربندی متفاوت یا پشتNginxبرود، این فایل ممکن است بخشی از قانونهایش را از دست بدهد.LiteSpeedبیشتر دستورهای آپاچی را میفهمد، ولی «بیشتر» با «همه» فرق دارد. - اگر فایل حذف شود، جابهجا شود، یا یک قانون بازنویسی جلوتر مسیر را عوض کند، محافظت بیصدا از بین میرود. هیچ خطایی نمیبینید؛ فقط یک روز فایلی که نباید، باز است.
- محافظت با
.htaccessجنس «پنهانکردن» دارد نه «نبودن». تنها چیزی که به یک فایل تنظیمات وابسته نیست، این است که فایل حساس اصلاً زیر ریشهی وب نباشد.
پس این قانونها را بهعنوان کمربند دوم بنویسید، نه بهعنوان راهحل. راهحل، همان چیدمانی است که در بخش «راه درست» گفتیم.
کش و فشردهسازی در همان فایل#
وقتی .htaccess را دست گرفتهاید، دو بلوک دیگر هم ارزش دارد. اول فشردهسازی:
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
AddOutputFilterByType DEFLATE application/javascript application/json
AddOutputFilterByType DEFLATE image/svg+xml
</IfModule>فهرست عمداً فقط شامل انواع متنی است. تصویرهای jpg و png و webp از قبل فشردهاند و گذاشتنشان در این فهرست فقط CPU مصرف میکند بدون اینکه حجم کم شود. image/svg+xml استثناست چون در واقع متن است.
بعد کش مرورگر:
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType image/webp "access plus 1 month"
ExpiresDefault "access plus 1 hour"
</IfModule>و اگر داراییهایتان نام هشدار دارند، هدر immutable هم منطقی است:
<IfModule mod_headers.c>
<FilesMatch "\.(css|js|woff2)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
</IfModule>یک هشدار جدی اینجا لازم است: این هدر را فقط روی فایلهایی بگذارید که با هر تغییر، نامشان عوض میشود — مثل خروجی Vite که هش را داخل نام فایل میگذارد. اگر همین قانون روی فایلی با نام ثابت مثل app.css بنشیند، مرورگر کاربر تا یک سال نسخهی قدیمی را نگه میدارد و هیچ Ctrl+F5 معمولی هم نجاتش نمیدهد. این دقیقاً همان تلهای است که در یادداشت کش immutable و نرسیدن CSS شرحش را نوشتهایم.
تست بعد از استقرار#
بعد از هر جابهجایی، این چند دستور را بزنید. کمتر از یک دقیقه وقت میگیرد و جلوی بدترین اتفاقها را میگیرد.
اول، مطمئن شوید فایلهای حساس از وب در دسترس نیستند. جواب درست 403 یا 404 است:
for p in /.env /storage/logs/laravel.log /composer.json /.git/config /vendor/autoload.php; do
echo -n "$p -> "
curl -s -o /dev/null -w "%{http_code}\n" "https://example.com$p"
doneاگر هر کدام 200 برگرداند، همینجا کار را متوقف کنید و چیدمان را درست کنید. بعدش هم APP_KEY را عوض کنید و رمز دیتابیس و کلیدهای سرویسها را از نو بسازید؛ چیزی که یک بار بیرون رفته، رفته است.
دوم، مطمئن شوید روتها واقعاً از .htaccess رد میشوند. صفحهی اول و یک آدرس داخلی را جدا تست کنید:
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/loginاگر اولی 200 بدهد و دومی 404، مشکل از قانونهای بازنویسی است نه از روتهای شما. سراغ بخش ۴۰۴ برگردید.
سوم، لینک فایلهای عمومی را چک کنید:
ls -l ~/app/public/storage
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/storage/test.txtخروجی ls -l باید یک لینک نشان بدهد که به ../storage/app/public اشاره میکند. اگر پوشهی معمولی بود یا اصلاً نبود، php artisan storage:link را دوباره بزنید.
چهارم، هدر کش یک دارایی هشدار را ببینید:
curl -sI https://example.com/build/assets/app-8f2b1c9d.css | grep -i cache-controlدرس اصلی#
مسئلهی public_html یک مسئلهی امنیتی است که لباس مسئلهی پیکربندی پوشیده. هر راهحلی که کارش «پنهان کردن» فایلهای پروژه از وب باشد، دیر یا زود شکست میخورد، چون به یک فایل تنظیمات و یک ماژول اختیاری وابسته است. تنها راهحلی که همیشه کار میکند این است که فایل حساس اصلاً زیر ریشهی وب نباشد.
و دربارهی ۴۰۴ها: قبل از باز کردن routes/web.php، اول تشخیص بدهید صفحهی خطا را آپاچی ساخته یا لاراول. این یک نگاه، مسیر عیبیابی را دو شاخه میکند و معمولاً چند ساعت را به چند دقیقه تبدیل میکند.