فایل htaccess شما خطای 500 میدهد و نمیدانید کدام خط مقصر است
تازه یک دستور به فایل .htaccess اضافه کردهاید و حالا سایت با 500 Internal Server Error از کار افتاده. اولین کاری که میکنید این است که فایل را از طریق FTP دانلود میکنید و خط به خط نگاه میکنید. مشکل اینجاست که Apache خطای دقیق را نشان نمیدهد و شما نمیدانید کدام دستور باعث شده کل سایت از دسترس خارج شود.
راه حل سریع: فایل را با نام دیگری مثل .htaccess.bak ذخیره کنید و یک فایل خالی جدید بسازید. سایت برمیگردد. حالا دستورها را یکییکی اضافه کنید تا خطای 500 برگردد. آخرین دستوری که اضافه کردهاید مقصر است. این روش تست خط به خط، تنها راهی است که در محیط اشتراکی جواب میدهد چون لاگ خطاهای Apache معمولاً در دسترس شما نیست.
ترتیب پردازش دستورات htaccess: جایی که همه چیز خراب میشود
Apache دستورات .htaccess را از بالا به پایین اجرا نمیکند. این تصور غلط رایج، منبع بیشتر خطاهای ریدایرکت است. قوانین RewriteRule به ترتیب ظاهری اجرا میشوند، اما قوانین Redirect از ماژول mod_alias دقیقاً برعکس عمل میکنند: آخرین قانون اول اجرا میشود.
این یعنی اگر دو قانون ریدایرکت داشته باشید که با هم تداخل دارند، نتیجه با چیزی که انتظار دارید فرق میکند. برای همین است که ترکیب Redirect و RewriteRule در یک فایل تقریباً همیشه نتیجه غیرقابل پیشبینی میدهد. اینجا اشتباه میکنند: یک قانون با Redirect 301 برای تغییر دامنه میگذارند و یک RewriteRule برای حذف www. نتیجه، ریدایرکتهای زنجیرهای و کندی سایت است.
قانون من را记住 کنید: برای هر کار فقط از یک ماژول استفاده کنید. اگر میخواهید ریدایرکت انجام دهید، همه قوانین را با RewriteRule بنویسید. اگر میخواهید صفحه خاصی را منتقل کنید، از Redirect استفاده کنید. قاطی کردن این دو، خطایی است که در عمل مدام میبینم.
دستورهای پایه که باید بلد باشید
قبل از هر چیز، ساختار پایه را بررسی کنیم. فایل .htaccess در ریشه public_html قرار میگیرد و روی همه زیرپوشهها اعمال میشود مگر اینکه خودشان فایل جدا داشته باشند. این رفتار آبشاری (cascading) یعنی یک دستور در ریشه، روی همه زیرشاخهها اثر میگذارد.
# فعال کردن موتور بازنویسی
RewriteEngine On
# تنظیم صفحه خطای سفارشی
ErrorDocument 404 /404.html
ErrorDocument 403 /forbidden.html
# مسدود کردن دسترسی به فایل htaccess
<Files ".htaccess">
Require all denied
</Files>
دستور Require all denied در Apache 2.4 جایگزین Deny from all شده که در نسخههای قدیمی استفاده میشد. اگر هاست شما از Apache 2.2 استفاده میکند، دستور جدید کار نمیکند و برعکس. برای تشخیص نسخه، یک فایل phpinfo.php بسازید و مقدار SERVER_SOFTWARE را ببینید.
ریدایرکتهای 301: انتقال دامنه و حذف www
ریدایرکت 301 به موتورهای جستجو میگوید که صفحه به صورت دائمی منتقل شده. این مهمترین دستوری است که در .htaccess مینویسید، مخصوصاً وقتی دامنه سایت را عوض میکنید. اگر این کار را اشتباه انجام دهید، سئوی چند ساله را از دست میدهید.
# انتقال از www به غیر www
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
# انتقال از HTTP به HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
دقت کنید که قانون حذف www باید قبل از قانون HTTPS بیاید. اگر برعکس باشد، ابتدا به HTTPS منتقل میشوید و بعد www حذف میشود که دو ریدایرکت پشت سر هم ایجاد میکند. هر ریدایرکت اضافه یعنی یک round-trip کامل و افزایش TTFB به اندازه 100 تا 300 میلیثانیه.
برای انتقال کامل دامنه به دامنه جدید، قانون سادهتر است:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^old-domain\.com$ [NC,OR]
RewriteCond %{HTTP_HOST} ^www\.old-domain\.com$ [NC]
RewriteRule ^(.*)$ https://new-domain.com/$1 [R=301,L]
اگر انتقال دامنه در پیش دارید، راهنمای تغییر دامنه سایت بدون افت سئو را بخوانید. این راهنما دقیقاً نشان میدهد چه ترتیبی برای ریدایرکتها باید رعایت کنید تا صفحههای ایندکسشده اعتبار خود را حفظ کنند.
کش کردن مرورگر و فشردهسازی: دو دستوری که سرعت را نجات میدهند
بیشتر سایتهای وردپرسی روی هاست اشتراکی، بدون هیچ کشی در سطح مرورگر اجرا میشوند. نتیجه این است که هر بازدیدکننده، تصاویر و فایلهای CSS و JavaScript را دوباره دانلود میکند. با دو بلاک ساده میتوانید این وضعیت را تغییر دهید.
# کش مرورگر برای فایلهای استاتیک
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>
# فشردهسازی Gzip
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css application/javascript image/svg+xml
</IfModule>
عدد یک سال برای تصاویر اغراقآمیز نیست. اگر نام فایل تصویر تغییر نکند، مرورگر تا یک سال از کش محلی استفاده میکند و درخواستی به سرور نمیفرستد. اما برای HTML هرگز کش یک ساله نگذارید، چون محتوای صفحه باید بهروز بماند.
اینجا اشتباه میکنند: ماژول mod_deflate روی همه هاستها فعال نیست. اگر دستور را بگذارید و ماژول نباشد، خطای 500 میگیرید. برای همین است که دستورها را داخل <IfModule> میگذاریم. این تگ به Apache میگوید اگر ماژول موجود نبود، بیصدا رد شو و خطا نده.
دستورهایی که در هاست اشتراکی مجاز نیستند
هاست اشتراکی یعنی شما یک tenant در سروری هستید که دهها سایت دیگر هم روی آن اجرا میشود. پس طبیعی است که بعضی دستورها برای حفظ امنیت و پایداری همه، غیرفعال شدهاند. این دستورها معمولاً با خطای 500 یا پیام Invalid command در لاگ مواجه میشوند.
| دستور | دلیل غیرفعال بودن | جایگزین |
|---|---|---|
php_value memory_limit |
تغییر حافظه اختصاصی PHP در سطح دایرکتوری | تغییر در php.ini یا درخواست از پشتیبانی |
Options +FollowSymLinks |
امنیت: اجازه دنبال کردن لینکهای سمبلیک | معمولاً از قبل فعال است یا نیازی نیست |
RewriteRule با target خارج از public_html |
دسترسی به فایلهای خارج از ریشه وب | انتقال فایل به داخل public_html |
SetEnvIf برای هدرهای خاص |
ممکن است با تنظیمات امنیتی سرور تداخل کند | استفاده از .user.ini یا php.ini |
اگر دستوری نوشتید و خطای 500 گرفتید، اول فرض را بر این بگذارید که دستور در سطح سرور غیرفعال است، نه اینکه syntax اشتباه باشد. یک راه سریع برای تست: دستور را داخل <IfModule> بگذارید. اگر خطا برطرف شد، یعنی ماژول یا دستور در دسترس نیست.
برای دیدن محدودیتهای دقیق منابع روی هاست اشتراکی، مرجع کامل محدودیتهای منابع هاست را بررسی کنید. این سند نشان میدهد هر عدد در پنل مدیریت چه چیزی را میشمارد و کجا به سقف میخورید.
محافظت از پوشهها با رمز عبور
گاهی لازم است یک پوشه را از دید عموم پنهان کنید، مثلاً محیط تست یا پنل مدیریت. روش استاندارد استفاده از .htpasswd است که خارج از public_html قرار میگیرد تا قابل دانلود نباشد.
# در فایل .htaccess داخل پوشه مورد نظر
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /home/username/.htpasswd
Require valid-user
مسیر /home/username/.htpasswd را با مسیر واقعی روی هاست خودتان عوض کنید. برای ساخت فایل رمز، از دستور زیر در SSH استفاده کنید:
htpasswd -c /home/username/.htpasswd admin
اگر SSH ندارید، ابزارهای آنلاین تولید .htpasswd هم کار میکنند، اما مطمئن شوید که هش رمز از نوع bcrypt یا apr1 باشد. هشهای قدیمی MD5 ساده در Apache 2.4 پشتیبانی نمیشوند و خطای احراز هویت میدهند.
راهنمای کامل رمزگذاری پوشه در هاست را ببینید اگر میخواهید این کار را برای محیط تست یا زیردامنه انجام دهید. این روش روی همه هاستهای لینوکس کار میکند و نیازی به افزونه یا ابزار جانبی ندارد.
مسدود کردن IP و جلوگیری از درخواستهای مخرب
اگر لاگ سرور را نگاه کنید، میبینید که رباتها و اسکنرها مدام در حال جستجوی مسیرهای آسیبپذیر هستند. مسدود کردن IPهای مشخص در سطح .htaccess ساده است اما محدودیت دارد.
# مسدود کردن IP مشخص
<RequireAll>
Require all granted
Require not ip 123.45.67.89
Require not ip 203.0.113.0/24
</RequireAll>
محدودیت این روش: اگر مهاجم از IP داینامیک استفاده کند یا از شبکههای مختلف بیاید، این فهرست بیفایده میشود. برای حملات واقعی، بهتر است از فایروال سطح برنامه مثل Wordfence یا ابزارهای امنیتی خود هاست استفاده کنید. .htaccess برای مسدود کردن چند IP مزاحم مشخص خوب است، نه برای دفاع در برابر حمله.
همچنین میتوانید هدرهای درخواست را بررسی کنید و رباتهای بد را مسدود کنید:
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (ahrefsbot|mj12bot|semrushbot) [NC]
RewriteRule ^ - [F,L]
دستور [F] کد 403 برمیگرداند و [L] یعنی پردازش قوانین بعدی را متوقف کن. این ترکیب برای مسدود کردن رباتهای خاص مؤثر است، اما مراقب باشید رباتهای مفید مثل Googlebot را اشتباهی مسدود نکنید.
رفع خطاهای رایج: صفحه سفید و حلقه ریدایرکت
دو خطایی که بیشتر از همه در پشتیبانی میبینیم، صفحه سفید و حلقه ریدایرکت است. صفحه سفید معمولاً ربطی به .htaccess ندارد و از خطای PHP میآید. اما گاهی یک دستور اشتباه مثل php_flag display_errors on میتواند مشکل را بدتر کند.
حلقه ریدایرکت اما تقریباً همیشه از .htaccess است. علامت آن در مرورگر: پیام ERR_TOO_MANY_REDIRECTS. علت معمول این است که قانون HTTPS شما روی درخواستهایی که قبلاً HTTPS هستند هم اعمال میشود. راه حل، افزودن شرط بررسی مجدد است:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
خط دوم شرط، هدر X-Forwarded-Proto را بررسی میکند. اگر سایت شما پشت CDN یا پروکسی باشد، این هدر نشان میدهد که اتصال اصلی HTTPS بوده و نباید دوباره ریدایرکت شود. بدون این شرط، بین سرور و CDN یک حلقه بینهایت ایجاد میشود.
اگر با صفحه سفید مواجه شدید، راهنمای رفع صفحه سفید در وردپرس و PHP را دنبال کنید. بیشتر مواقع مشکل از حافظه PHP است، نه .htaccess. اما اگر بعد از تغییر .htaccess صفحه سفید شد، اول فایل را به حالت قبل برگردانید.
تست دستورات قبل از اعمال روی سایت اصلی
هرگز دستور جدید را مستقیم روی سایت اصلی تست نکنید. یک پوشه تست بسازید، فایل .htaccess را آنجا بگذارید و رفتار را بررسی کنید. این کار دو دقیقه وقت میگیرد و از یک ساعت خطایابی در امانتان میکند.
برای بررسی اینکه آیا قوانین ریدایرکت درست کار میکنند، از ابزار خط فرمان curl استفاده کنید:
curl -I -L https://example.com/old-page
خروجی، تمام ریدایرکتهای زنجیرهای را نشان میدهد. اگر بیش از دو ریدایرکت پشت سر هم دیدید، یعنی قانونهای شما بهینه نیستند. هر ریدایرکت اضافه، زمان بارگذاری را برای کاربران واقعی افزایش میدهد.
برای بررسی DNS و اطمینان از اینکه رکوردها درست تنظیم شدهاند، از ابزار بررسی DNS و شبکه استفاده کنید. گاهی مشکل ریدایرکت از DNS است، نه .htaccess. اگر دامنه به سرور قدیمی اشاره کند، هر چقدر هم قانون درست بنویسید، کاربر به جای درست نمیرسد.
وقتی htaccess جواب نمیدهد: چه کار کنید
گاهی دستور درست است، syntax هم درست است، اما هیچ اثری ندارد. این معمولاً یعنی Apache اجازه پردازش .htaccess را در آن دایرکتوری ندارد. تنظیم AllowOverride None در کانفیگ سرور، تمام فایلهای .htaccess را بیاثر میکند.
در هاست اشتراکی، این وضعیت نادر است اما ممکن است برای بعضی پوشهها رخ دهد. تست ساده: یک خطای عمدی در فایل بگذارید، مثلاً یک کاراکتر اضافه. اگر سایت خطای 500 نداد، یعنی فایل اصلاً خوانده نمیشود. اگر خطا داد، فایل در حال پردازش است و مشکل جای دیگری است.
اگر روی سرور اختصاصی هستید، میتوانید تنظیمات AllowOverride را در فایل کانفیگ مجازی هاست تغییر دهید. اما روی هاست اشتراکی، این کار دست شما نیست و باید با پشتیبانی تماس بگیرید. بیشتر مواقع، مشکل از جای دیگری است: کش سایت. اگر از کش صفحه استفاده میکنید، تغییرات .htaccess ممکن است تا زمان پاک شدن کش اعمال نشود.
برای بررسی اینکه آیا سایت شما از کش سمت سرور استفاده میکند که دستورات را بیاثر کرده، هدرهای پاسخ را با curl -I بررسی کنید. اگر هدر X-Cache: HIT یا مشابه آن را دیدید، کش را پاک کنید و دوباره تست کنید.
پرسشهای پرتکرار
چرا بعد از تغییر htaccess خطای 500 میگیرم؟
خطای 500 بعد از تغییر .htaccess تقریباً همیشه از یک خطای syntax یا استفاده از دستوری است که روی هاست شما غیرفعال است. فایل را با نام دیگر ذخیره کنید تا سایت برگردد، سپس دستورها را یکییکی اضافه کنید تا خطای 500 برگردد. آخرین دستوری که اضافه کردهاید مقصر است.
فرق Redirect و RewriteRule در htaccess چیست؟
Redirect از ماژول mod_alias است و فقط برای انتقال ساده از یک URL به URL دیگر استفاده میشود. RewriteRule از mod_rewrite است و امکان شرطگذاری با RewriteCond را دارد. برای ریدایرکتهای شرطی مثل حذف www یا انتقال به HTTPS، از RewriteRule استفاده کنید. ترکیب این دو در یک فایل نتیجه غیرقابل پیشبینی میدهد.
چطور در htaccess ریدایرکت 301 از HTTP به HTTPS انجام دهم؟
قانون استاندارد این است: RewriteCond %{HTTPS} off و بعد RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]. اگر سایت پشت CDN است، شرط دوم RewriteCond %{HTTP:X-Forwarded-Proto} !https را هم اضافه کنید تا حلقه ریدایرکت ایجاد نشود.
آیا میتوانم در htaccess محدودیت حافظه PHP را تغییر دهم؟
دستور php_value memory_limit در بیشتر هاستهای اشتراکی غیرفعال است چون امنیت سرور را به خطر میاندازد. اگر به حافظه بیشتری نیاز دارید، از پشتیبانی هاست بخواهید یا از فایل .user.ini استفاده کنید. روی هاست لینوکس سرورنت این محدودیتها شفاف اعلام شده و میدانید دقیقاً چه مقداری در اختیار دارید.