دسته بندی:

آموزش ریدایرکت در htaccess: ریدایرکت ۳۰۱، اجبار www و جلوگیری از هات‌لینک

یک صفحه را جابه‌جا کرده‌اید و لینک‌های قدیمی به خطای ۴۰۴ می‌رسند. یا سایت هم با example.com بالا می‌آید هم با www.example.com و گوگل دو نسخه از هر صفحه می‌بیند. یا متوجه شده‌اید تصویرهای سایتتان مستقیم روی یک سایت دیگر نمایش داده می‌شود و پهنای باند شما را مصرف می‌کند. هر سهٔ این‌ها با یک فایل حل می‌شود: .htaccess در روت سایت.

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

قدم صفر: فایل و پیش‌نیازهایش

نام فایل دقیقاً .htaccess است؛ با نقطه شروع می‌شود و پسوند دیگری ندارد. محل درستش روت سایت است، یعنی همان پوشه‌ای که فایل شاخص (index.php یا index.html) در آن قرار دارد و در اکثر هاست‌های اشتراکی اسمش public_html است. چون نام فایل با نقطه شروع می‌شود، در File Manager سی‌پنل تا وقتی گزینهٔ نمایش فایل‌های مخفی (Show Hidden Files) را فعال نکنید آن را نمی‌بینید. اگر هنوز با ماهیت این فایل آشنا نیستید، ابتدا توضیح کامل اینکه htaccess چیست را بخوانید.

دو پیش‌نیاز روی سرور وجود دارد:

  • فایل باید مجاز باشد. در پیکربندی وب‌سرور باید برای مسیر سایت شما AllowOverride فعال باشد؛ برای دستورهای ریدایرکت حداقلِ لازم AllowOverride FileInfo است و معمولاً AllowOverride All تنظیم می‌شود. اگر این مقدار None باشد، فایل شما بی‌سروصدا نادیده گرفته می‌شود و هیچ خطایی هم نمی‌بینید.
  • ماژول mod_rewrite باید فعال باشد. دستورهای RewriteEngine، RewriteCond و RewriteRule بدون این ماژول کار نمی‌کنند. روی هاست اشتراکی معمولاً از قبل فعال است و اگر نبود باید از پشتیبانی هاست بخواهید؛ روی سرور مجازی یا اختصاصی خودتان می‌توانید بررسی و فعالش کنید.
# AlmaLinux / Rocky Linux / CentOS
httpd -M | grep rewrite

# Debian / Ubuntu
apache2ctl -M | grep rewrite
a2enmod rewrite
systemctl restart apache2

در RHEL و مشتقاتش (AlmaLinux و Rocky Linux) این ماژول به‌طور پیش‌فرض بارگذاری شده است و بعد از هر تغییر در پیکربندی خودِ سرور (فایل httpd.conf و ویرچوال‌هاست‌ها) کافی است سرویس را با systemctl restart httpd ری‌استارت کنید. اما دقت کنید: تغییر .htaccess هیچ ری‌استارتی نمی‌خواهد. آپاچی این فایل را در هر درخواست دوباره می‌خواند و قانون جدید بلافاصله اعمال می‌شود؛ پس روی سرور زنده بی‌دلیل سرویس را ری‌استارت نکنید. اگر در آموزش‌های قدیمی دستورهایی مثل service httpd restart یا /etc/init.d/httpd restart دیدید، آن‌ها مربوط به نسل قبلی سیستم‌عامل‌ها هستند؛ روی توزیع‌های امروزی معادل درستشان systemctl restart httpd (یا systemctl restart apache2) است.

یک نکتهٔ مهم دیگر: .htaccess مخصوص آپاچی و LiteSpeed است. اگر وب‌سرور شما Nginx باشد این فایل اصلاً خوانده نمی‌شود و همین قوانین باید در کانفیگ خود Nginx نوشته شوند.

دو ابزار ریدایرکت در htaccess: Redirect و RewriteRule

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

دستور Redirect (ماژول mod_alias)

ساده‌ترین حالت. وقتی محتوای سایت را جابه‌جا کرده‌اید و می‌خواهید بازدیدکننده به جای صفحهٔ قدیمی محتوای جدید را ببیند، همین کافی است:

Redirect 301 /old_dir/old.html https://www.example.com/new_dir/index.html

به جای example.com دامنهٔ خودتان را بگذارید. نکته‌ای که در نمونه‌های قدیمی گفته نمی‌شد این است که Redirect روی پیشوند مسیر کار می‌کند، نه فقط روی همان یک آدرس. یعنی اگر بنویسید:

Redirect 301 /old_dir/ https://www.example.com/new_dir/

باقی مسیر هم به انتهای مقصد اضافه می‌شود و درخواست /old_dir/a/b.html به /new_dir/a/b.html می‌رود. این معمولاً همان چیزی است که برای انتقال یک پوشهٔ کامل می‌خواهید، ولی اگر انتظارش را نداشته باشید نتیجه گیج‌کننده می‌شود. اگر می‌خواهید فقط یک الگوی مشخص را با عبارت باقاعده هدف بگیرید، از RedirectMatch استفاده کنید:

RedirectMatch 301 ^/old_dir/(.*).html$ https://www.example.com/new_dir/$1/

دستور RewriteRule (ماژول mod_rewrite)

وقتی ریدایرکت شما شرط دارد — به نام دامنه، به پروتکل، به Referer یا به کوئری‌استرینگ بستگی دارد — دیگر Redirect کافی نیست و باید سراغ mod_rewrite بروید:

RewriteEngine On
RewriteRule ^old_dir/old.html$ https://www.example.com/new_dir/ [R=301,L]

دو تفاوت نوشتاری را همین‌جا به خاطر بسپارید: در Redirect مسیر با اسلش ابتدایی نوشته می‌شود (/old_dir/…) اما در RewriteRule داخل فایل .htaccess آن اسلش ابتدایی حذف می‌شود (^old_dir/…). و در الگوی RewriteRule نقطه یک کاراکتر خاص است، پس نقطهٔ واقعی را باید با بک‌اسلش اسکیپ کنید.

یک توصیهٔ عملی: برای یک مسیر مشخص هم‌زمان از هر دو ماژول قانون ننویسید. این دو مستقل از هم اجرا می‌شوند و ترکیبشان نتیجهٔ غیرقابل پیش‌بینی می‌دهد. برای هر مسیر یکی را انتخاب کنید.

۳۰۱ یا ۳۰۲؟

عدد بعد از دستور، کد وضعیتی است که به مرورگر و موتور جستجو اعلام می‌شود:

  • ۳۰۱ یعنی دائمی. آدرس قدیمی دیگر برنمی‌گردد. موتورهای جستجو اعتبار صفحهٔ قدیمی را به آدرس جدید منتقل می‌کنند و مرورگرها این ریدایرکت را به‌شدت کش می‌کنند.
  • ۳۰۲ یعنی موقت. آدرس قدیمی قرار است برگردد؛ مثلاً صفحه در حال تعمیر است یا کاربر را موقتاً به یک صفحهٔ کمپین می‌فرستید.

دو تلهٔ رایج اینجاست. اول اینکه اگر عدد را ننویسید — یعنی Redirect /old /new یا فلگ [R] بدون عدد — آپاچی به‌طور پیش‌فرض ۳۰۲ برمی‌گرداند، نه ۳۰۱. در نمونه‌کدهای قدیمی همین اشتباه زیاد دیده می‌شود؛ اگر ریدایرکت شما دائمی است حتماً صریحاً R=301 بنویسید. دوم اینکه چون مرورگر ۳۰۱ را کش می‌کند، اگر یک بار آدرس اشتباه را با ۳۰۱ اعلام کنید، اصلاح‌کردنش برای کاربرانی که یک بار آن را دیده‌اند دردسر دارد. موقع آزمایش اول با ۳۰۲ کار کنید و وقتی مطمئن شدید آن را به ۳۰۱ تبدیل کنید.

کدهای ۳۰۷ و ۳۰۸ هم وجود دارند و انتخاب بین آن‌ها و تأثیر هر کدام بر رتبهٔ سایت بحث جداگانه‌ای است که در مقالهٔ ریدایرکت ۳۰۱ دامنه و قوانین سئو به آن پرداخته‌ایم.

یکسان‌سازی www و بدون www

اگر سایت شما هم با example.com باز شود و هم با www.example.com، از دید موتور جستجو دو سایت با محتوای یکسان دارید و از دید کاربر دو نشانی متفاوت. باید یکی را انتخاب کنید و بقیه را به آن بفرستید. کدام یکی؟ فرقی نمی‌کند؛ مهم این است که انتخاب کنید و همان یکی را همه‌جا به کار ببرید.

برای اجبار به www، این را در ابتدای .htaccess بگذارید:

RewriteEngine On
RewriteCond %{HTTP_HOST} !^www.example.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [QSA,L,R=301]

و برای حالت برعکس، یعنی حذف www:

RewriteEngine On
RewriteCond %{HTTP_HOST} ^www.example.com$ [NC]
RewriteRule ^(.*)$ https://example.com/$1 [QSA,L,R=301]

دو چیز را در این کدها نسبت به نسخه‌های قدیمی که در اینترنت می‌بینید تغییر داده‌ایم و هر دو مهم‌اند. نقطه‌های داخل RewriteCond با بک‌اسلش اسکیپ شده‌اند؛ نقطهٔ اسکیپ‌نشده در عبارت باقاعده یعنی «هر کاراکتری»، پس شرط شما دامنه‌هایی مثل wwwXexample.com را هم می‌گیرد. و مقصد https:// است نه http://؛ اگر سایت شما SSL دارد، ریدایرکت به http یک پرش اضافه می‌سازد و کاربر لحظه‌ای روی اتصال رمزنگاری‌نشده می‌رود.

اگر می‌خواهید هم‌زمان HTTPS را هم اجباری کنید، آن را در همان یک قانون جمع کنید تا زنجیرهٔ ریدایرکت درست نشود:

RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www.example.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [QSA,L,R=301]

هشدار: اگر سایت شما پشت یک CDN یا پروکسی معکوس است که SSL را خودش پایان می‌دهد و با http به سرور شما وصل می‌شود، متغیر %{HTTPS} روی سرور همیشه off دیده می‌شود و این قانون حلقهٔ بی‌نهایت می‌سازد. در آن حالت باید به جای آن شرط RewriteCond %{HTTP:X-Forwarded-Proto} !https را بررسی کنید.

انتقال دامنه به یک ساب‌دامنه

همین الگو برای فرستادن کل ترافیک یک دامنه به ساب‌دامنه هم کار می‌کند:

RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www.)?example.com$ [NC]
RewriteRule ^(.*)$ https://sub.example.com/$1 [R=301,L]

قسمت ^(.*)$ و $1 باعث می‌شود مسیر درخواست حفظ شود؛ نمونه‌های قدیمی معمولاً مقصد را بدون مسیر می‌نوشتند و نتیجه این بود که همهٔ صفحه‌ها به صفحهٔ اول ساب‌دامنه می‌افتادند — که هم برای کاربر بد است هم برای سئو.

وردپرس و حلقهٔ بی‌پایان ریدایرکت

این قانون‌ها با سیستم‌های مدیریت محتوا مثل وردپرس هم بدون مشکل کار می‌کنند؛ فقط آن‌ها را بالای بلوک # BEGIN WordPress بگذارید تا هنگام بازنویسی آن بلوک توسط وردپرس پاک نشوند.

مهم‌تر از آن، آدرسی که در تنظیمات خود سیستم مدیریت محتوا ثبت شده باید دقیقاً همانی باشد که در .htaccess اجبار کرده‌اید. در وردپرس این دو مقدار در «تنظیمات ← عمومی» با نام «نشانی وردپرس» و «نشانی سایت» قرار دارند. اگر در htaccess به www اجبار کرده باشید ولی در تنظیمات آدرس بدون www نوشته باشد، هر درخواست بین این دو گیر می‌کند و مرورگر خطای «تعداد ریدایرکت‌ها بیش از حد است» می‌دهد.

جلوگیری از هات‌لینک تصاویر

هات‌لینک یعنی سایت دیگری به جای آپلود تصویر روی هاست خودش، مستقیم به آدرس تصویر شما لینک می‌دهد. نتیجه این است که هر بار صفحهٔ آن‌ها باز می‌شود، فایل از سرور شما دانلود می‌شود؛ پهنای باند شما تمام می‌شود بدون آنکه حتی یک بازدید به سایت شما اضافه شده باشد. جلوگیری از این کار هم با mod_rewrite انجام می‌شود، پس باز هم باید این ماژول فعال باشد.

ساده‌ترین حالت، مسدود کردن کامل است:

RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www.)?example.com/ [NC]
RewriteRule .(gif|jpe?g|png|webp|svg)$ - [F]

خط به خط: شرط اول درخواست‌هایی را که Referer خالی دارند کنار می‌گذارد — یعنی وقتی کسی آدرس تصویر را مستقیم در مرورگر باز می‌کند یا مرورگرش به دلایل حریم خصوصی این هدر را نمی‌فرستد، تصویر همچنان نمایش داده می‌شود. اگر این خط را بردارید بخشی از کاربران واقعی هم تصویر را نخواهند دید. شرط دوم درخواست‌هایی را که از خود سایت شما آمده‌اند مستثنا می‌کند. و [F] در خط آخر یعنی پاسخ ۴۰۳ Forbidden.

نسبت به نمونه‌های قدیمی سه چیز اینجا اصلاح شده است: الگوی Referer به جای http:// با https?:// نوشته شده (وگرنه روی سایت دارای SSL خودتان هم قانون فعال می‌شود و تصاویر خودتان از کار می‌افتند)، نقطهٔ دامنه اسکیپ شده، و پسوندهای امروزی مثل png و webp اضافه شده‌اند. فهرست پسوندها هم دست خودتان است؛ اگر فایل‌های دیگری مثل css، pdf یا ویدیوهایتان هم هات‌لینک می‌شوند، آن‌ها را به همین پرانتز اضافه کنید. اگر دامنه‌های دیگری دارید یا از CDN استفاده می‌کنید، برای هر کدام یک خط RewriteCond دیگر با همان الگو بنویسید؛ و اگر می‌خواهید تصاویرتان در گوگل ایمیجز یا پیش‌نمایش شبکه‌های اجتماعی دیده شود، دامنهٔ آن سرویس‌ها را هم به همین شکل مستثنا کنید.

نمایش تصویر جایگزین

به جای خطای ۴۰۳ می‌توانید تصویر دیگری نشان دهید — مثلاً یک بنر که می‌گوید این تصویر متعلق به سایت شماست:

RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www.)?example.com/ [NC]
RewriteCond %{REQUEST_URI} !^/hotlink.jpg$
RewriteRule .(gif|jpe?g|png)$ https://www.example.com/hotlink.jpg [R=302,L]

خط سوم را حتماً نگه دارید. نمونه‌های قدیمی این خط را ندارند و نتیجه یک حلقهٔ بی‌نهایت است: مرورگر به hotlink.jpg هدایت می‌شود، آن فایل هم پسوند jpg دارد و Referer هنوز همان سایت خارجی است، پس دوباره ریدایرکت می‌شود و همین‌طور ادامه پیدا می‌کند. شرط REQUEST_URI خودِ تصویر جایگزین را از قانون بیرون می‌گذارد.

دو نکتهٔ پایانی دربارهٔ هات‌لینک: اینجا ۳۰۲ منطقی‌تر از ۳۰۱ است، چون نمی‌خواهید مرورگرها برای همیشه آدرس تصویر شما را معادل بنر جایگزین کش کنند. و بدانید که هدر Referer به‌سادگی قابل جعل است؛ این روش یک صرفه‌جویی در پهنای باند است، نه یک سازوکار امنیتی برای محافظت از فایل‌ها.

تست کردن و برگرداندن تغییرات

قبل از هر ویرایشی یک نسخهٔ پشتیبان بگیرید. این ساده‌ترین بیمه‌نامه است:

cp .htaccess .htaccess.bak

برای غیرفعال کردن سریع فایل هم لازم نیست محتوایش را پاک کنید؛ کافی است اسمش را عوض کنید تا آپاچی دیگر آن را نبیند:

mv .htaccess .htaccess.off

یک هشدار دربارهٔ همین کار: اگر سایت شما وردپرس (یا هر سیستم مدیریت محتوای دیگری با پیوند یکتای زیبا) است، با نبودن .htaccess فقط صفحهٔ اول بالا می‌آید و بقیهٔ آدرس‌ها ۴۰۴ می‌شوند، چون قوانین بازنویسی خودِ وردپرس هم در همین فایل‌اند. پس این کار را فقط برای چند لحظه و برای فهمیدن اینکه ریشهٔ مشکل همین فایل است انجام دهید، و بلافاصله نسخهٔ پشتیبان را برگردانید (mv .htaccess.off .htaccess). اگر محتوای فایل از دست رفت، وردپرس با ذخیرهٔ دوبارهٔ «تنظیمات ← پیوندهای یکتا» بلوک پیش‌فرض خودش را از نو می‌سازد.

برای تست، به مرورگر اعتماد نکنید؛ مرورگر ریدایرکت ۳۰۱ را کش می‌کند و ممکن است نتیجهٔ قدیمی را نشانتان بدهد. کد وضعیت واقعی را با curl ببینید:

curl -I https://example.com/old_dir/old.html

و برای دیدن کل زنجیرهٔ ریدایرکت تا مقصد نهایی:

curl -sIL https://example.com/old_dir/old.html | grep -i "^HTTP/|^location:"

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

وقتی خطای ۵۰۰ می‌گیرید

خطای ۵۰۰ Internal Server Error بلافاصله بعد از ویرایش .htaccess تقریباً همیشه یعنی یکی از دستورهای فایل غلط نوشته شده یا هاست اجازهٔ آن دستور را نمی‌دهد. متن دقیق خطا در لاگ آپاچی است:

# AlmaLinux / Rocky Linux / CentOS
tail -n 50 /var/log/httpd/error_log

# Debian / Ubuntu
tail -n 50 /var/log/apache2/error.log

روی هاست اشتراکی، همین لاگ در سی‌پنل از بخش Errors در دسترس است. توجه کنید که apachectl configtest فقط پیکربندی اصلی سرور را بررسی می‌کند و خطاهای داخل .htaccess را نشان نمی‌دهد.

یک مورد خاص که زیاد پیش می‌آید: نمونه‌کدهای قدیمی گاهی خط Options +SymLinksIfOwnerMatch را بالای قوانین می‌گذارند. این دستور برای کار کردن نیاز به AllowOverride Options دارد و بسیاری از هاست‌های اشتراکی آن را نمی‌دهند؛ نتیجه خطای ۵۰۰ است. در بیشتر موارد این خط لازم نیست و با حذفش مشکل حل می‌شود.

اگر روی سرور خودتان هستید و می‌خواهید ببینید mod_rewrite دقیقاً چه می‌کند، در پیکربندی سرور (نه در .htaccess) از LogLevel alert rewrite:trace3 استفاده کنید. دستور قدیمی RewriteLog در آپاچی ۲.۴ حذف شده و اگر آن را بنویسید سرور بالا نمی‌آید.

جمع‌بندی

خلاصهٔ کاری که باید انجام دهید: از فایل نسخهٔ پشتیبان بگیرید، برای جابه‌جایی ساده از Redirect 301 و برای هر چیز شرط‌دار از RewriteRule با فلگ صریح R=301 استفاده کنید، یکی از دو حالت www یا بدون www را انتخاب کنید و همان را در تنظیمات سیستم مدیریت محتوا هم بنویسید، هات‌لینک را با استثنا کردن Referer خالی و دامنهٔ خودتان ببندید، و در پایان با curl -I مطمئن شوید کد وضعیت و تعداد پرش‌ها همان است که انتظار دارید. کاربردهای دیگر این فایل — از صفحهٔ خطای سفارشی و رمزگذاری پوشه‌ها گرفته تا تنظیم‌های PHP — را در مطلب کاربردهای فایل htaccess دنبال کنید.