Debian.pro

Блог для пользователей и администраторов Debian


Большой мануал: часть 21. Учимся делать редиректы в nginx.

Эта статья — часть Большого Мануала по настройке lamp-сервера на debian.

Предыдущая часть цикла — Создаём конфиг для нашего сайта в nginx.

Следующая часть цикла — Not yet published

В этой статье мы поговорим о том, как в nginx нынче модно делать редиректы. Точнее, цель даже не в том, чтобы научить делать редиректы (это всё мелочи), а в том, чтобы вы после прочтения статьи точно поняли, что вы написали только что в конфиге (и зачем).

Для начала нужно узнать, как nginx выбирает нужный server{} для обработки запроса. Первым делом, разберемся с важным моментом в районе listen. Дело в том, что в nginx можно написать по-разному:

  • listen 80;
  • listen 192.168.0.1:80;

Так вот. Очень важно знать, что если вы где-то в конфиге написали listen ip:80, то для этой связки IP+порта ни один server{} с listen 80 уже работать не будет (ну только если написать 2 параметра listen =)).
Переводя для таких же тупых, как я. Если у вас на сервере много хостов и все они с listen 80, то не вздумайте писать хоть в одном конфиге listen ip:80. Nginx начнет отвечать на все запросы к этому ip на 80м порту одним вхостом. То есть на все запросы станет показывать один и тот же сайт. Я так продакшн большой ломал, ага

Так вот. Имея на руках ip и порт, nginx выбирает все server{} с максимально совпадающим listen. Дальше по server_name начинает выбирать конкретный server{} из списка. server_name может быть записан в трех видах:

  • т.н. «exact name», (server_name www.example.com;)
  • wildcard (server_name *.example.com;, отдельной группой обрабатываются имена со звездочкой на конце, а не в начале).
  • регулярки (server_name ~^(?.+)\.example\.com$;)

Regex-ы, кстати, приятны тем, что при помощи них из Host можно сразу выделить кусочек и использовать его как переменную (собственно, в примере это и показано, ну или поищите в блоге по слову evhost).
Но важно на самом деле другое. Все эти 3 группы server_name обрабатываются отдельно. То есть, если у вас имя хоста указано явным образом в одном из конфигов (exact name), то nginx не будет пытаться искать то же самое в остальных именах (в wildcard-ах или regex-ах). То бишь, www.example.com (прописанный в одном конфиге) уже исключен из *.example.com в другом конфиге. И то же самое с регэкспами — в регэкспы уже точно не попадают те хосты, которые записаны в виде wildcard или точного совпадения.

Вот. Когда nginx исходя из listen и server_name уже выберет конкретный server{} для обработки запроса — то запрос будет обрабатываться именно в нём. В лайти, например, можно было написать конфиг так, что концов не сыщешь %).

Теперь стоит узнать об основных переменных, которые могут быть полезны в простейших манипуляциях с конфигом. В первую очередь, нам тут интересны те переменные, в которые попадают различные части URL запроса:

  • $scheme — протокол запроса (http/https)
  • $host — заумно описанная переменная в документации, не используйте её, в неё может оказаться ЁНХ (кстати, она ещё и не эскейпится, что чревато багами в нашем конфиге)
  • $http_host — это значение http-заголовка Host: , её и рекомендую использовать в конфигах.
  • $uri — это часть URL после корня (после хост, то бишь) без аргументов. Именно эта часть URL-а матчится с location-ами.
  • $args — это переменная, содержащая всю строку аргументов (всё, что после ? в URL).
  • $arg_x — это переменная, содержащая значение url-аргумента x (для ?x=bla, $arg_x будет содержать bla).
  • $request_uri — переменная, содержащая и $uri, и $args (то есть весь урл, кроме хоста и прото).

Теперь о том, как лучше делать редиректы. Если есть возможность — то используйте return, а не rewrite (все примеры ниже будут именно про return). Стоит помнить, что 301й кода ответа кешируется браузером (то есть браузер запомнит, что по этому URL был такой редирект и сразу пройдет по редиректу из кеша). 302й код ответа браузерам кешировать запрещено (в семье не без урода, конечно, но вообще это достаточно надежное условие). Везде в примерах я использую 302й редирект, чтобы вы случайно копипастя не сделали «вечный» редирект. И вообще я советую все редиректы писать именно 302-е, а менять на 301-е только после того, как вы убедитесь, что всё работает верно (ну и если вы уверены, что этот редирект навсегда).

Вот. Теперь, вооружившись этими знаниями, мы понапишем всяких странных и не очень редиректов.
Первый пример. Хост www.example.com редиректим на example.com, сохраняя URL.

server {
    listen 80;
    server_name www.example.com;
    location / { return 302 http://example.com$request_uri; }
}



Второй пример. Хост www.example.com редиректим на example.com, сохраняя протокол запроса, но не сохраняя URL (ну то есть всегда на морду example.com в том же протоколе).

server {
    include listen;
    include listen_ssl;
    server_name www.example.com
    location / { return 302 $scheme://example.com/ ; }
}



Третий пример. Все хосты *.example.com редиректим на https (на всякий случай, такой конфиг будет работать только если нет другого конфига для хостов вида *.example.com (в том числе и отдельно описанных, например www.example.com), в которых было бы прописано listen 80). URL сохраняем.

server {
    listen 80;
    server_name *.example.com;
    location / { return 302 https://$http_host$request_uri; }
}



Четвертый пример. Если в URL есть аргумент lang и его значение равно en, то редиректить нужно на en.example.com, сохранив URL без аргументов.

server {
    listen 80;
    server_name example.com;
    location / {
        if ($arg_lang = en) { return 302 http://en.example.com$uri ; }
    }
}



Да, если вам нужно сделать rewrite, исходя из наличия аргумента — то без if-а не обойтись. В остальных случаях лучше пользоваться приёмом из первых примеров — отделяем запрос в отдельный location внутри конкретного server{} и внутри него делаем return.
Ах да, лучше писать location / с редиректом внутри, даже если другой конфигурации в этом server{} сейчас не будет. Если потом вы решите дописать что-то, а return прописан у вас прямо в server{} — то потом можно поседеть.

05.02.2017 byinkvizitor68sl|big-manual

Комментарии (5):

  1. Tallanvor :

    За arg_x — отдельное спасибо, был не в курсе.

  2. http_host можно же манипулировать, насколько безопасно полагаться на эту переменную?
    http://www.opennet.ru/opennews/art.shtml?num=46499

  3. Дык вы статью то прочитайте.
    Там описан случай, когда wordpress слушает default host. А в мануале как раз описано, как default host завести.

    Anyway, можно писать и статический хост вместо переменной, но тогда конфиг на каждый редирект разный будет.

  4. Кажется, что вы не поняли вопроса. Может это немного прояснит?
    https://github.com/yandex/gixy/blob/master/docs/ru/plugins/hostspoofing.md

  5. > Кажется, что вы не поняли вопроса.
    Я вопрос прекрасно понял.
    Повторяю — спуфить $http_host при правильно описанных server_name (без слишком широко трактуемых регулярок, например) — бесполезно, запрос уйдет в default host, а там на запрос ответят 403/404/whatever.

    Так что свести все ваши беспокойства можно к трем пунктам:
    1) нет регулярок в server_name?
    2) значение $http_host не передаётся напрямую в бэкэнд?
    3) мы сейчас не описываем default host для сокета?
    Тогда можно использовать $http_host.

    А вот использовать $host где-то, кроме как для передачи на бэкэнд — к лишним восклицаниям «какого хера?!» в процессе эксплуатации сервиса приводит, серьёзно.

Написать комментарий