Хочу сегодня рассмотреть тему редиректов в Nginx. Я обычно настраиваю их в лоб. То есть нужен какой-то редирект, я его добавляю. На днях меня попросили СЕО специалисты переработать редиректы одного проекта и сделать так, чтобы для клиента был ровно один 301 редирект, который включает в себя сразу все необходимые преобразования url.
На углубленном курсе "Архитектура современных компьютерных сетей" вы с нуля научитесь работать с Wireshark и «под микроскопом» изучите работу сетевых протоколов. На протяжении курса надо будет выполнить более пятидесяти лабораторных работ в Wireshark.
Пример двойного редиректа
Для того, чтобы было понятно, о чем идет речь, приведу пример. Допустим, у вас настроен редирект с http на https и добавление к урлу в конце слеш. То есть вы хотите такое преобразование:
http://site.ru/catalog -> https://site.ru/catalog/
Допустим, у вас сначала был настроен редирект на https подобным образом:
server { listen 80; root /var/www/site.ru/public; location / { return 301 https://site.ru$request_uri; } }
А потом вас попросили добавить редирект всех урлов без слеша на тот же урл только со слешем на конце. Вы идете в секцию c listen 443 и добавляете редирект.
server { listen 443 http2; ................... location / { rewrite ^([^.]*[^/])$ $1/ permanent; ................... }
В целом все нормально, редиректы работают. Но если перейти по ссылке http://site.ru/catalog, мы получим 2 301-х редиректа.
# curl -I -L http://site.ru/catalog HTTP/1.1 301 Moved Permanently Server: nginx Content-Type: text/html Content-Length: 162 Connection: keep-alive Location: https://site.ru/catalog HTTP/2 301 server: nginx content-type: text/html content-length: 162 location: https://site.ru/catalog/ HTTP/2 200 server: nginx content-type: text/html; charset=UTF-8 vary: Accept-Encoding
На выходе у вас 2 редиректа вместо одного, что плохо для СЕО. Надо по возможности все реализовать в одном. В данном случае напрашивается простое и очевидное решение:
server { listen 80; server_name site.ru www.site.ru; root /var/www/site.ru/public; location / { return 301 https://site.ru$request_uri/; } }
Вроде бы все нормально. Теперь редирект будет автоматически добавлять слеш в конец запроса. Но проблемы начнутся со ссылками на медиа файлы. Например, запрос http://site.ru/catalog/img.png будет превращаться в https://site.ru/catalog/img.png/, что нам совершенно не нужно. Чтобы это исправить, надо сделать так.
server { listen 80; server_name site.ru www.site.ru; location ~* ^.+.(js|css|png|jpg|jpeg|gif|webp|ico|woff|txt)$ { return 301 https://site.ru$request_uri; } location / { return 301 https://site.ru$request_uri/; } }
Теперь все будет нормально, так как location со статикой указан в виде регулярного выражения. В случае попадания запроса в указанное правило, будет выполнен редирект без слеша. Все остальное попадет в следующий префиксный location /. То же самое можно сделать с помощью if и одного location, но c if работать будет медленнее. Там где можно обходиться без if, лучше его не использовать.
Пример с nginx rewrite
Теперь другая проблема. Возьмем такой url - http://site.ru/catalog/. В текущем конфиге он превращается в https://site.ru/catalog//. Исправляем это:
server { listen 80; server_name site.ru www.site.ru; location ~* ^.+.(js|css|png|jpg|jpeg|gif|webp|ico|woff)$ { return 301 https://site.ru$request_uri; } location / { rewrite ^/(.*)/$ /$1; return 301 https://site.ru$uri/; } }
Обращаю внимание на то, что сделано. Я использую rewrite без какого-либо флага на конце, чтобы не прекращать обработку директив. В данном случае просто меняется uri и передается дальше. Если запрос приходит со слешом на конце, мы его обрезаем и отправляем в правило редиректа на https. Если слеша нет, то он сразу на редирект уходит. Теперь все в порядке.
Встроенные редиректы WordPress
Очевидно, что выше описана простая ситуация. На нее достаточно обратить внимание и исправить. Но не всегда бывает так просто. Например, вы сами не настраивали редирект урлов без слеша на урлы со слешом, он вам не нужен. Но, к примеру, WordPress реализует подобный редирект своими средствами. В итоге, при запросе http://site.ru/catalog вы получите такую картину с редиректами.
# curl -I -L http://site.ru/catalog HTTP/1.1 301 Moved Permanently Server: nginx Content-Type: text/html Content-Length: 162 Connection: keep-alive Location: https://site.ru/catalog HTTP/2 301 server: nginx content-type: text/html; charset=UTF-8 location: https://site.ru/catalog/ x-powered-by: PHP/7.4.2 x-redirect-by: WordPress HTTP/2 200 server: nginx content-type: text/html; charset=UTF-8 vary: Accept-Encoding
Сначала nginx сделал редирект на https, так как вы это настроили у него в конфигурации, а потом wordpress на страницу со слешом на конце. В итоге у вас два редиректа, а надо один. Причем, два редиректа получились не по вашей воле. Если не обратите на это внимание, так и будете с ними жить. По факту, все типовые редиректы лучше сразу реализовывать в одном месте в веб сервере.
Все стандартные редиректы в nginx
Рассмотрю типовой пример, когда у нас одновременно присутствуют следующие редиректы:
- С http на https.
- С www на без www для обоих протоколов.
- Без слеша на конце на урл со слешем.
Наша цель будет реализовать все преобразования url в одном месте и выдать клиенту только один 301-й редирект.
server { listen 443 ssl http2; server_name site.ru; root /web/sites/site.ru/www/; index index.php index.html index.htm; access_log /web/sites/site.ru/log/access.log main; error_log /web/sites/site.ru/log/error.log; ssl_certificate /etc/letsencrypt/live/site.ru/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/site.ru/privkey.pem; location / { rewrite ^([^.]*[^/])$ $1/ permanent; try_files $uri/ /index.php?$args; } location ~* ^.+.(js|css|png|jpg|jpeg|gif|webp|ico|woff|txt)$ { access_log off; expires max; } location ~* ^/(\.ht|xmlrpc\.php)$ { return 404; } location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/var/run/php-fpm/php7-fpm.sock; fastcgi_index index.php; fastcgi_param DOCUMENT_ROOT /web/sites/site.ru/www/; fastcgi_param SCRIPT_FILENAME /web/sites/site.ru/www$fastcgi_script_name; fastcgi_param PATH_TRANSLATED /web/sites/site.ru/www$fastcgi_script_name; include fastcgi_params; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param HTTPS on; fastcgi_intercept_errors on; } location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { allow all; log_not_found off; access_log off; } } server { listen 443 ssl http2; server_name www.site.ru; location ~* ^.+.(js|css|png|jpg|jpeg|gif|webp|ico|woff|txt)$ { return 301 https://site.ru$request_uri; } location / { rewrite ^/(.*)/$ /$1; return 301 https://site.ru$uri/; } } server { listen 80; server_name site.ru www.site.ru; location ~* ^.+.(js|css|png|jpg|jpeg|gif|webp|ico|woff|txt)$ { return 301 https://site.ru$request_uri; } location / { rewrite ^/(.*)/$ /$1; return 301 https://site.ru$uri/; } }
Получилось примерно так. Призываю не копировать бездумно конфиг, а проверить то, что я предлагаю. Хотя я сам внимательно проверил, как мог, но все равно не застрахован от ошибки. На мой взгляд здесь рассмотрены все основные моменты с редиректами. На выходе всегда один 301 редирект, какой бы запрос мы не сделали. При этом все реализовано средствами самого веб сервера, а значит, будет работать максимально быстро.
Корректный редирект с одного url на другой
Допустим вы корректно настроили стандартные редиректы в nginx. А потом в какой-то момент у вас поменялась структура сайта, или просто нужно было сделать редиректы для отдельных страниц. К примеру, запрос https://site.ru/main/hello/ перенаправить в https://site.ru/main/. По идее ничего сложного. Добавляем редирект:
server { listen 443; ........................ location /main/hello { return 301 /main/; } ........................
Если делать запросы по https, то все в порядке. Никаких проблем, сработает ровно один 301-й редирект на другой url. А что будет при запросе http://site.ru/main/hello ? Смотрим.
# curl -I -L --insecure http://site.ru/main/hello HTTP/1.1 301 Moved Permanently Server: nginx Content-Type: text/html Content-Length: 162 Connection: keep-alive Location: https://site.ru/main/hello/ HTTP/2 301 server: nginx content-type: text/html content-length: 162 location: https://site.ru/main/ HTTP/2 200 server: nginx content-type: text/html; charset=UTF-8 vary: Accept-Encoding
Опять два 301-х редиректа. Переделываем на один, не забывая все возможные варианты написания.
server { listen 443 ssl http2; server_name www.site.ru; location ~* ^.+.(js|css|png|jpg|jpeg|gif|webp|ico|woff|txt)$ { return 301 https://site.ru$request_uri; } location / { rewrite ^/(.*)/$ /$1; return 301 https://site.ru$uri/; } location /main/hello { rewrite ^/(.*)/$ /$1; return 301 https://site.ru/main/; } } server { listen 80; server_name site.ru www.site.ru; location ~* ^.+.(js|css|png|jpg|jpeg|gif|webp|ico|woff|txt)$ { return 301 https://site.ru$request_uri; } location / { rewrite ^/(.*)/$ /$1; return 301 https://site.ru$uri/; } location /main/hello { rewrite ^/(.*)/$ /$1; return 301 https://site.ru/main/; } }
Ну и так далее. Думаю, идея ясна. Следует следить за всеми редиректами и стараться всегда оставлять только один.
Заключение
Я прилично заморочился с темой редиректов в nginx. Раньше никогда не обращал на них пристального внимания. Да и у других не видел акцента на этом. В интернете полно готовых вариантов перенаправлений на все случаи жизни, но рассмотрены они в отдельности. А вот так комплексно взглянуть на полный конфиг со всеми нюансами не приходилось.
Материал полностью написан и протестирован мной от начала до конца, поэтому призываю не копировать слепо к себе, а проверить. Я могу где-то ошибаться, что в такой важной теме может быть чревато проблемами с индексацией и работой сайта. Так что внимательно все проверяйте, прежде чем внедрять у себя. Ну а замечания все, как обычно, жду в комментариях.
В завершении рекомендую мою статью про настройку nginx. Я там частично рассматриваю и эту тему. А вообще там рассказаны все основные моменты, на которые стоит обращать внимание при работе с nginx.
На углубленном курсе "Архитектура современных компьютерных сетей" вы с нуля научитесь работать с Wireshark и «под микроскопом» изучите работу сетевых протоколов. На протяжении курса надо будет выполнить более пятидесяти лабораторных работ в Wireshark.
Здравствуйте, я начинающий и не пойму почему тут location ~* ^.+.(js|css|png|jpg|jpeg|gif|webp|ico|woff)$ не экранирован символ . отделяющий имя файла от расширения. Спасибо.
Доброго дня подскажите как сделать 301
если у страницы два (три, 4,5 итд) слеша на конце надо на один перенаправить
sait.ru//
sait.ru///
Подскажите кто знает как это сделать
А не должно быть так в location / ?
try_files $uri $uri/ /index.php?$args;
Где именно? Не понял вопрос.
Вот здесь
location / {
rewrite ^([^.]*[^/])$ $1/ permanent;
try_files $uri/ /index.php?$args;
}
Это для listen 443 ssl http2;
Получается при такой записи, что обычные файлы не будут открываться. Например, если в корне создать test.txt, то при запросе
https://site.ru/test.txt отдает 404 ошибку
Или это только у меня так....
Так мы же как раз первым правилом rewrite добавляем всем / на конец. Вообще, я сейчас уже не готов обсуждать эти правила, надо погружаться в тему. Статью писал, проверяя все эти правила. У меня был заказ на настройку всех этих редиректов, так что примеры брал с рабочего проекта.
Добрый вечер коллеги!
Помогите сделать редирект на nginx(192.168.15.133).
Есть url: https://domain.company.ru/index.php/s/zaa7wyHieLKjdWc
/etc/nginx/sites-available/default
server {
listen 80;
server_name 192.168.15.133;
return 301 https://domain.company.ru/index.php/s/zaa7wyHieLKjdWc$request_uri;
}
с таким конфигом при обращении на 192.168.15.133, открывается https://domain.company.ru/index.php/login (просит авторизоваться)
если же просто набрать https://domain.company.ru/index.php/s/zaa7wyHieLKjdWc откроется нужная страница.
Что нужно поправить? Спасибо.
Скажите, пожалуйста, у домена несколько алиасов.
server_name site1.ru site2.ru site3.ru
Мне нужно редиретктить site1.ru/page например на google.com
А site2.ru/landing Редиректить на яндекс.ру
if ($http_host = 'site1.ru') {
rewrite ^(.*) https:/google.com permanent;
}
if ($http_host = 'site2.ru') {
rewrite ^(.*) https://yandex.ru permanent;
}
Есть еще вариант чтобы одним блоком обойтись?
Дополню статью.
Мне регулярно надо делать редирект из папки на другой сайт: SITE.NAME/FOLDER_NAME ==> EXAMPLE.COM
Около года собирала все возможные костыли, и мой грааль выглядит сейчас так. Работает с http и с https.
Размещать это необходимо до блока "location /"
Что-то не смог разгадать этот грааль. А почему простой вариант не подходит?
1) Первый блок обрабатывает случай, если юзер идет на SITE.NAME/FOLDER_NAME и перекидывает юзера на SITE.NAME/FOLDER_NAME/ (да, это два редиректа, и можно вписать туда тот же конфиг, что и во втором, но для моих нужд так удобнее)
2) Второй блок смотрит на SITE.NAME/FOLDER_NAME/bla/bla/bla/index.php и перекидывает всё на EXAMPLE.COM/bla/bla/bla/index.php вместе со всеми GET'ами и POST'ами.
Владимир, добрый день.
Подскажите, а здесь разве не получится, как с http?
--quote--
Вроде бы все нормально. Теперь редирект будет автоматически добавлять слеш в конец запроса. Но проблемы начнутся со ссылками на медиа файлы. Например, запрос http://site.ru/catalog/img.png будет превращаться в https://site.ru/catalog/img.png/, что нам совершенно не нужно. Чтобы это исправить, надо сделать так.
--quote--
Нет. Здесь картинки в отдельном location c regexp, в котором нет правила добавления слеша. Этот location проверяется раньше основного.