Thank you for reading this post, don't forget to subscribe!
Допустим, у вас настроен редирект с http на https и добавление к урлу в конце слеш. То есть вы хотите такое преобразование:
1 |
http://site.ru/catalog -> http<strong>s</strong>://site.ru/catalog<strong>/</strong> |
Допустим, у вас сначала был настроен редирект на https подобным образом:
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 |
server { listen 80; root /var/www/site.ru/public; location / { return 301 https://site.ru$request_uri; } } |
[/codesyntax]
А потом вас попросили добавить редирект всех урлов без слеша на тот же урл только со слешем на конце. Вы идете в секцию c listen 443 и добавляете редирект.
[codesyntax lang="php"]
1 2 3 4 5 6 7 |
server { listen 443 http2; ................... location / { rewrite ^([^.]*[^/])$ $1/ permanent; ................... } |
[/codesyntax]
В целом все нормально, редиректы работают. Но если перейти по ссылке http://site.ru/catalog, мы получим 2 301-х редиректа.
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 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 |
[/codesyntax]
На выходе у вас 2 редиректа вместо одного, что плохо для СЕО. Надо по возможности все реализовать в одном. В данном случае напрашивается простое и очевидное решение:
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 |
server { listen 80; server_name site.ru www.site.ru; root /var/www/site.ru/public; location / { return 301 https://site.ru$request_uri/; } } |
[/codesyntax]
Вроде бы все нормально. Теперь редирект будет автоматически добавлять слеш в конец запроса. Но проблемы начнутся со ссылками на медиа файлы. Например, запрос http://site.ru/catalog/img.png будет превращаться в https://site.ru/catalog/img.png/, что нам совершенно не нужно. Чтобы это исправить, надо сделать так.
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 |
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/; } } |
[/codesyntax]
Теперь все будет нормально, так как location со статикой указан в виде регулярного выражения. В случае попадания запроса в указанное правило, будет выполнен редирект без слеша. Все остальное попадет в следующий префиксный location /. То же самое можно сделать с помощью if и одного location, но c if работать будет медленнее. Там где можно обходиться без if, лучше его не использовать.
Пример с nginx rewrite
Теперь другая проблема. Возьмем такой url - http://site.ru/catalog/. В текущем конфиге он превращается в https://site.ru/catalog//. Исправляем это:
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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/; } } |
[/codesyntax]
Я использую rewrite без какого-либо флага на конце, чтобы не прекращать обработку директив. В данном случае просто меняется uri и передается дальше. Если запрос приходит со слешом на конце, мы его обрезаем и отправляем в правило редиректа на https. Если слеша нет, то он сразу на редирект уходит. Теперь все в порядке.
Встроенные редиректы WordPress
Например, вы сами не настраивали редирект урлов без слеша на урлы со слешом, он вам не нужен. Но, к примеру, WordPress реализует подобный редирект своими средствами. В итоге, при запросе http://site.ru/catalog вы получите такую картину с редиректами.
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# 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 |
[/codesyntax]
Сначала nginx сделал редирект на https, так как вы это настроили у него в конфигурации, а потом wordpress на страницу со слешом на конце. В итоге у вас два редиректа, а надо один. Причем, два редиректа получились не по вашей воле. Если не обратите на это внимание, так и будете с ними жить. По факту, все типовые редиректы лучше сразу реализовывать в одном месте в веб сервере.
Все стандартные редиректы в nginx
Рассмотрю типовой пример, когда у нас одновременно присутствуют следующие редиректы:
- С http на https.
- С www на без www для обоих протоколов.
- Без слеша на конце на урл со слешем.
Наша цель будет реализовать все преобразования url в одном месте и выдать клиенту только один 301-й редирект.
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
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/; } } |
[/codesyntax]
Получилось примерно так. На выходе всегда один 301 редирект, какой бы запрос мы не сделали. При этом все реализовано средствами самого веб сервера, а значит, будет работать максимально быстро.
Корректный редирект с одного url на другой
Допустим вы корректно настроили стандартные редиректы в nginx. А потом в какой-то момент у вас поменялась структура сайта, или просто нужно было сделать редиректы для отдельных страниц. К примеру, запрос https://site.ru/main/hello/ перенаправить в https://site.ru/main/. По идее ничего сложного. Добавляем редирект:
[codesyntax lang="php"]
1 2 3 4 5 6 7 |
server { listen 443; ........................ location /main/hello { return 301 /main/; } ........................ |
[/codesyntax]
Если делать запросы по https, то все в порядке. Никаких проблем, сработает ровно один 301-й редирект на другой url. А что будет при запросе http://site.ru/main/hello ? Смотрим
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 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 |
[/codesyntax]
Опять два 301-х редиректа. Переделываем на один, не забывая все возможные варианты написания.
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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/; } } |
[/codesyntax]