Nginx. Установка и настройка. Часть 3 из 3
Переменные в файле конфигурации
В конфигурационном файле Nginx могут встречаться переменные, вместо которых на этапе выполнения подставляются их значения. Пользовательские переменные создаются с использованием директив set
и map
. Кроме того, имеются также предопределенные переменные, устанавливаемые Nginx.
Предопределенные переменные
В модуле ngx_http_core_module
определены следующие переменные
Имя переменной | Значение переменной |
---|---|
$arg_name |
Значение GET-параметра name запроса |
$args |
Все GET-параметры запроса |
$binary_remote_addr |
ip-адрес клиента в двоичном виде |
$content_length |
Значение заголовка запроса Content-Length |
$content_type |
Значение заголовка запроса Content-Type |
$cookie_name |
Значение cookie с именем name |
$document_root |
Значение директивы root или alias для текущего запроса |
$document_uri |
Псевдоним переменной $uri |
$host |
Значение заголовка запроса Host , если таковой присутствует. В противном случае это значение, заданное в директиве server_name , отвечающей запросу. |
$hostname |
Имя хоста, на котором работает Nginx |
$http_name |
Значение заголовка запроса с именем name . Если имя заголовка содержит дефисы, они преобразуются в знаки подчеркивания. Прописные буквы преобразуются в строчные. |
$https |
Для соединений, шифруемых по протоколу SSL, принимает значение on . В противном случае пустая строка. |
$is_args |
Если в запросе есть аргументы, принимает значение ? . В противном случае пустая строка. |
$limit_rate |
Значение, заданное в директиве limit_rate . Если значение не установлено, то с помощью этой переменной можно регулировать скорость отдачи содержимого клиентам. |
$nginx_version |
Номер версии работающей программы nginx |
$pid |
Идентификатор рабочего процесса |
$query_string |
Псевдоним переменной $args |
$realpath_root |
Значение директивы root или alias для текущего запроса после разрешения всех символических ссылок. |
$remote_addr |
ip-адрес клиента |
$remote_port |
Номер порта клиента |
$remote_user |
При использовании простой схемы аутентификации по HTTP эта переменная содержит имя пользователя. |
$request |
Полный запрос, полученный от клиента, включая метод HTTP, URI-адрес, версию протокола HTTP, заголовки и тело. |
$request_body |
Тело запроса для использования в location , обрабатываемых директивами *_pass . |
$request_body_file |
Путь к временному файлу, в котором сохранено тело запроса. Для создания этого файла директива client_body_in_file_only должна иметь значение on . |
$request_completion |
Если запрос получен полностью, принимает значение OK , иначе пустая строка. |
$request_filename |
Путь к файлу для текущего запроса, формируемый на основе значения директивы root или alias и URI-адреса. |
$request_method |
HTTP-метод текущего запроса. |
$request_uri |
Полный URI, указанный в запросе, включая GET-параметры |
$scheme |
Схема текущего запроса — HTTP или HTTPS |
$sent_http_name |
Значение заголовка ответа с именем name . Если имя заголовка содержит дефисы, они преобразуются в знаки подчеркивания. Прописные буквы преобразуются в строчные. |
$server_addr |
Адрес сервера, принявшего запрос. |
$server_name |
Значение директивы server_name виртуального сервера, принявшего запрос. |
$server_port |
Номер порта сервера, принявшего запрос. |
$server_protocol |
Версия протокола HTTP для текущего запроса. |
$status |
Код состояния ответа. |
$uri |
Нормализованный URI текущего запроса. |
Пользовательские переменные
Создать переменную и установить значение можно с помощью директив конфигурации set
и map
. Директива set
позволяет присвоить указанной переменной (первый аргумент) заданное значение (второй аргумент). Директива map
устанавливает значение переменной (второй аргумент), в зависимости от значения строки (первый аргумент). Первый параметр внутри блока map
задает возможное значение первого аргумента, второй параметр — значение второго аргумента, если было совпадение. Первый параметр может быть как строкой, так и регулярным выражением (в этом случае начинается на ~
или ~*
).
Первый пример, директива set
Можно установить значение переменной через директиву set
server { listen 80; server_name example.com www.example.com; default_type text/plain; if ($arg_one = "111") { set $var_one "GET parameter one = $arg_one"; } location / { if ($var_one) { return 200 "Variable: $var_one"; } return 200 "Variable has no value"; } }
$ curl -i http://example.com/?one=111 HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Mon, 05 Feb 2024 12:38:57 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 34 Connection: keep-alive Variable: GET parameter one = 111
$ curl -i http://example.com HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Mon, 05 Feb 2024 12:39:45 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 21 Connection: keep-alive Variable has no value
Второй пример, директива map
Можно установить значение переменной через директиву map
map $arg_one $var_one { "111" "GET parameter one = 111"; } server { listen 80; server_name example.com www.example.com; default_type text/plain; location / { if ($var_one) { return 200 "Variable: $var_one"; } return 200 "Variable has no value"; } }
$ curl -i http://example.com/?one=111 HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Mon, 05 Feb 2024 12:45:33 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 33 Connection: keep-alive Variable: GET parameter one = 111
$ curl -i http://example.com HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Mon, 05 Feb 2024 12:46:27 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 21 Connection: keep-alive Variable has no value
Третий пример, директива set
Можно установить переменной одно из нескольких значений, используя сравнение
server { listen 80; server_name example.com www.example.com; default_type text/plain; if ($arg_debug = "on") { set $var_debug "GET parameter debug = on"; } if ($arg_debug = "yes") { set $var_debug "GET parameter debug = yes"; } location / { if ($var_debug) { return 200 "Variable: $var_debug"; } return 200 "Variable has no value"; } }
$ curl -i http://example.com/?debug=yes HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Mon, 05 Feb 2024 13:03:28 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 35 Connection: keep-alive Variable: GET parameter debug = yes
$ curl -i http://example.com HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Mon, 05 Feb 2024 13:04:10 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 21 Connection: keep-alive Variable has no value
Четвертый пример, директива map
Можно установить переменной одно из нескольких значений, используя сравнение
map $arg_debug $var_debug { "on" "GET parameter debug = on"; "yes" "GET parameter debug = yes"; } server { listen 80; server_name example.com www.example.com; default_type text/plain; location / { if ($var_debug) { return 200 "Variable: $var_debug"; } return 200 "Variable has no value"; } }
$ curl -i http://example.com/?debug=yes HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Mon, 05 Feb 2024 13:03:28 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 35 Connection: keep-alive Variable: GET parameter debug = yes
$ curl -i http://example.com HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Mon, 05 Feb 2024 13:04:10 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 21 Connection: keep-alive Variable has no value
Фазы обработки запроса
Nginx обрабатывает запросы с использованием нескольких фаз. Это полезно знать, чтобы понять работу директивы rewrite
, которая будет ниже.
NGX_HTTP_SERVER_REWRITE_PHASE
— фаза преобразования URI запроса на уровне виртуального сервераNGX_HTTP_FIND_CONFIG_PHASE
— фаза поиска контекста запроса (поиск location)NGX_HTTP_REWRITE_PHASE
— фаза преобразования URI запроса на уровне locationNGX_HTTP_POST_REWRITE_PHASE
— фаза обработки результатов преобразования URI запросаNGX_HTTP_PREACCESS_PHASE
— фаза подготовки данных для проверки ограничений доступа к ресурсуNGX_HTTP_ACCESS_PHASE
— фаза проверки ограничений доступа к ресурсуNGX_HTTP_POST_ACCESS_PHASE
— фаза обработки результатов проверки ограничений доступа к ресурсуNGX_HTTP_CONTENT_PHASE
— фаза генерации ответаNGX_HTTP_LOG_PHASE
— фаза записи в лог
Модуль ngx_http_rewrite_module
Модуль ngx_http_rewrite
предоставляет директивы if
, set
, rewrite
, return
, break
. Эти директивы, указанные внутри секции server
, выполняются последовательно. Потом эти директивы выполняются последовательно внутри найденного подходящего location
. Если URI был перезаписан, то выполняется новый поиск подходящего location
— и директивы опять выполняются последовательно.
Эти директивы выполняются на фазе перезаписи URI — и не выполняются на фазе генерации ответа. Если условие директивы if
выполняется — то будут выполнены все директивы модуля ngx_http_rewrite
внутри if
. На фазе генерации ответа — повтроная проверка условия директивы if
не выполняется. Если условие if
выполнилось на фазе перезаписи — на фазе генерации ответа выполняются директивы внутри if
, не связанные с модулем ngx_http_rewrite
(например, add_header
).
Директива конфигурации if
В качестве условия может быть сравнение переменной со строкой с помощью операторов =
и !=
, соответствие переменной регулярному выражению с помощью операторов ~
(с учетом регистра) и ~*
(без учета регистра), проверка существования файла с помощью операторов -f
и !-f
, проверка существования каталога с помощью операторов -d
и !-d
. Кроме того, в качестве условия может быть использовано имя переменной — ложными значениями являются пустая строка или ноль.
if ($http_user_agent ~ "MSIE") { rewrite ^(.*)$ /msie/$1 break; } if ($http_cookie ~* "id=([^;]+)(?:;|$)") { set $id $1; } if ($request_method = "POST") { return 405; } if ($slow) { limit_rate 10k; } if ($invalid_referer) { return 403; }
!~
(с учетом регистра) и !~*
(без учета регистра).
Важно понимать, как работает директива if
внутри location
. Фактически, если условие выполняется на фазе перезаписи URI — создается еще один location
. На фазе генерации ответа выполняются директивы внутри этого location
+ унаследованные директивы, директивы вне if
— не выполняются. Наследование директив создает впечатление, что директивы вне if
тоже выполняются. И следует помнить, что на каждой фазе обработки запроса выполняются свои директивы.
Первый пример
При выполнении любого http-запроса проверяется ip-адрес клиента. При этом, заголовок X-debug
отправляется всегда — потому что при выполнении директив внутри if
будет выполнена унаследованная директива add_header
.
server { listen 80; server_name example.com www.example.com; default_type text/plain; location / { if ($remote_addr = "192.168.110.2") { return 200 "ip-address 192.168.110.2"; } add_header X-Debug "Example if directive" always; return 200 "ip-address not match"; } }
Выполняем http-запрос с ip-адреса 192.168.110.2
$ curl -i http://example.com HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sun, 04 Feb 2024 10:56:31 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 24 Connection: keep-alive X-Debug: Example if directive ip-address 192.168.110.2
Выполняем http-запрос с ip-адреса 192.168.110.3
$ curl -i http://example.com HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sun, 04 Feb 2024 11:12:43 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 20 Connection: keep-alive X-Debug: Example if directive ip-address not match
Второй пример
Немного изменим конфигурацию, чтобы директива add_header
не наследовалась от location
. Это можно сделать, если добавить внутрь if
еще одну директиву add_header
.
server { listen 80; server_name example.com www.example.com; default_type text/plain; location / { if ($remote_addr = "192.168.110.2") { add_header X-Debug "Example if directive one" always; return 200 "ip-address 192.168.110.2"; } add_header X-Debug "Example if directive two" always; return 200 "ip-address not match"; } }
Выполняем http-запрос с ip-адреса 192.168.110.2
$ curl -i http://example.com HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sun, 04 Feb 2024 11:17:22 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 24 Connection: keep-alive X-Debug: Example if directive one ip-address 192.168.110.2
Выполняем http-запрос с ip-адреса 192.168.110.3
$ curl -i http://example.com HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sun, 04 Feb 2024 11:20:20 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 20 Connection: keep-alive X-Debug: Example if directive two ip-address not match
Третий пример
Давайте уберем директиву return
внутри директивы if
. В этом случае директива if
унаследует директиву return
от location
. То есть, при выполнении условия — отработает директива add_header
внутри if
и унаследованная директива return
из location
.
server { listen 80; server_name example.com www.example.com; default_type text/plain; location / { if ($remote_addr = "192.168.110.2") { add_header X-Debug "Example if directive one" always; # return 200 "ip-address 192.168.110.2"; } add_header X-Debug "Example if directive two" always; return 200 "ip-address not match"; } }
Выполняем http-запрос с ip-адреса 192.168.110.2
$ curl -i http://example.com HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sun, 04 Feb 2024 11:35:48 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 20 Connection: keep-alive X-Debug: Example if directive one ip-address not match
Выполняем http-запрос с ip-адреса 192.168.110.3
$ curl -i http://example.com HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sun, 04 Feb 2024 11:39:22 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 20 Connection: keep-alive X-Debug: Example if directive two ip-address not match
Четвертый пример
На фазе перезаписи URI — Nginx последовательно выполнит все директивы модуля перезаписи. То есть, set
, if
, set
и set
. На фазе генерации ответа — Nginx попадает внутрь if
(фактически, это еще один location
). Поскольку внутри if
нет обработчика содержимого — будет унаследован внешний return
.
server { listen 80; server_name example.com www.example.com; default_type text/plain; location / { set $debug "111"; if ($debug = "111") { set $debug "222"; add_header X-Debug "Inner header, debug = $debug" always; } set $debug "333"; add_header X-Debug "Outer header, debug = $debug" always; return 200 "Variable debug = $debug"; } }
$ curl -i http://example.com HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Mon, 26 Feb 2024 11:51:40 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 20 Connection: keep-alive X-Debug: Inner header, debug = 333 Variable debug = 333
Пятый пример
Давайте добавим директиву return
внутрь директивы if
— и посмотрим, что получится. Результат неожиданный — директива return
прерывает последовательное выполнение директив модуля перезаписи. Так что последняя директива set $debug "333"
не выполняется.
server { listen 80; server_name example.com www.example.com; default_type text/plain; location / { set $debug "111"; if ($debug = "111") { set $debug "222"; add_header X-Debug "Inner header, debug = $debug" always; return 200 "Inner return, debug = $debug"; } set $debug "333"; add_header X-Debug "Outer header, debug = $debug" always; return 200 "Outer return, debug = $debug"; } }
$ curl -i http://example.com HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Mon, 26 Feb 2024 11:46:47 GMT Content-Type: text/plain; charset=UTF-8 Content-Length: 25 Connection: keep-alive X-Debug: Inner header, debug = 222 Inner return, debug = 222
Директива конфигурации rewrite
Директива принимает два обязательных аргумента — регулярное выражение и строка замены.
rewrite регулярное_выражение строка_замены [флаг];
Если регулярное выражение соответствует URI запроса, URI изменяется в соответствии со строкой замены. Директивы rewrite
выполняются последовательно, в порядке их следования в конфигурационном файле. С помощью флагов можно прекратить дальнейшую обработку директив. Если строка замены начинается с http|https
или $scheme
, то обработка завершается и клиенту возвращается перенаправление.
Необязательный аргумент флаг может принимать значения
last
— завершает обработку текущего набора директив модуляrewrite
, поиск новогоlocation
по новому URIbreak
— завершает обработку текущего набора директив модуляrewrite
аналогично директивеbreak
redirect
— возвращает временное перенаправление с кодом 302 (Moved Temporarily)permanent
— возвращает постоянное перенаправление с кодом 301 (Moved Permanently)
Директива rewrite
может выполнять два типа перенаправлений — внутренние и внешние. Внутренний редирект — это замена URI + поиск нового location
. Внешний редирект — отправка клиенту HTTP-кода 301 или 302, которые предписывают запросить новый URL. Признак внешнего редиректа — наличие флага redirect|permanent
или строка замены начинается на http|https
(или $scheme
).
Первый пример
Здесь есть два внутренних перенаправления. Первая директива rewrite
перезаписывает URI на /page-two.html
— после чего происходит поиск нового location
. Вторая директива rewrite
перезаписывает URI на /page-end.html
— после чего происходит поиск нового location
. Последним будет найден третий location
, который вернет клиенту содержимое файла page-end.html
. При этом с точки зрения клиента — он просматривает страницу http://example.com/page-one.html
.
server { listen 80; server_name example.com www.example.com; root /var/www/example.com/; index index.php index.html; location = /page-one.html { rewrite ^ /page-two.html last; } location = /page-two.html { rewrite ^ /page-end.html last; } location / { try_files $uri $uri/ =404; } # записываем в лог-файл перенаправления rewrite_log on; error_log /var/log/nginx/error.log notice; }
$ curl -i -L http://example.com/page-one.html HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sun, 03 Mar 2024 09:10:09 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 188 Last-Modified: Mon, 26 Feb 2024 12:48:22 GMT Connection: keep-alive ETag: "65dc8896-bc" Accept-Ranges: bytes <!DOCTYPE html> <html> <head> <title>Page example.com/page-end.html</title> </head> <body> <h1>File /var/www/example.com/page-end.html</h1> </body> </html>
$ cat /var/log/nginx/error.log ...: *1 "^" matches "/page-one.html", client: 192.168.110.2, server: example.com, request: "GET /page-one.html HTTP/1.1", host: "example.com" ...: *1 rewritten data: "/page-two.html", args: "", client: 192.168.110.2, server: example.com, request: "GET /page-one.html HTTP/1.1", host: "example.com" ...: *1 "^" matches "/page-two.html", client: 192.168.110.2, server: example.com, request: "GET /page-one.html HTTP/1.1", host: "example.com" ...: *1 rewritten data: "/page-end.html", args: "", client: 192.168.110.2, server: example.com, request: "GET /page-one.html HTTP/1.1", host: "example.com"
Второй пример
Здесь есть два внешних перенаправления. Первая директива rewrite
отправляет клиенту http-код 302 с указанием запросить новый URL. Клиент запрашивает новый URL — срабатывает вторая директива rewrite
. Которая отправляет клиенту http-код 302 с указанием запросить новый URL. Клиент опять запрашивает новый URL — в этот раз будет найден третий location
, который вернет клиенту содержимое файла page-end.html
.
server { listen 80; server_name example.com www.example.com; root /var/www/example.com/; index index.php index.html; location = /page-one.html { rewrite ^ /page-two.html redirect; } location = /page-two.html { rewrite ^ /page-end.html redirect; } location / { try_files $uri $uri/ =404; } # записываем в лог-файл перенаправления rewrite_log on; error_log /var/log/nginx/error.log notice; }
$ curl -i -L http://example.com/page-one.html HTTP/1.1 302 Moved Temporarily Server: nginx/1.18.0 (Ubuntu) Date: Sun, 03 Mar 2024 09:33:31 GMT Content-Type: text/html Content-Length: 154 Location: http://example.com/page-two.html Connection: keep-alive HTTP/1.1 302 Moved Temporarily Server: nginx/1.18.0 (Ubuntu) Date: Sun, 03 Mar 2024 09:33:31 GMT Content-Type: text/html Content-Length: 154 Location: http://example.com/page-end.html Connection: keep-alive HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sun, 03 Mar 2024 09:33:31 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 188 Last-Modified: Mon, 26 Feb 2024 12:48:22 GMT Connection: keep-alive ETag: "65dc8896-bc" Accept-Ranges: bytes <!DOCTYPE html> <html> <head> <title>Page example.com/page-end.html</title> </head> <body> <h1>File /var/www/example.com/page-end.html</h1> </body> </html>
$ cat /var/log/nginx/error.log ...: *1 "^" matches "/page-one.html", client: 192.168.110.2, server: example.com, request: "GET /page-one.html HTTP/1.1", host: "example.com" ...: *1 rewritten redirect: "/page-two.html", client: 192.168.110.2, server: example.com, request: "GET /page-one.html HTTP/1.1", host: "example.com" ...: *1 "^" matches "/page-two.html", client: 192.168.110.2, server: example.com, request: "GET /page-two.html HTTP/1.1", host: "example.com" ...: *1 rewritten redirect: "/page-end.html", client: 192.168.110.2, server: example.com, request: "GET /page-two.html HTTP/1.1", host: "example.com"
Третий пример
Нужно выполнять редиректы для нескольких старых страниц сайта — поскольку были созданы новые версии этих страниц. Это внешние редиректы с кодом 301 — мы сообщаем клиенту, что страницы перемещены постоянно, старые не нужно запрашивать.
map $uri $redirect { default ""; "/old-foo.html" "/new-foo.html"; "/old-bar.html" "/new-bar.html"; "/old-baz.html" "/new-baz.html"; } server { listen 80; server_name example.com www.example.com; root /var/www/example.com/; index index.php index.html; if ($redirect) { rewrite ^ $redirect permanent; # return 301 $redirect; } location / { try_files $uri $uri/ =404; } # записываем в лог-файл перенаправления rewrite_log on; error_log /var/log/nginx/error.log notice; }
$ curl -i -L http://example.com/old-foo.html HTTP/1.1 301 Moved Permanently Server: nginx/1.18.0 (Ubuntu) Date: Sun, 03 Mar 2024 10:23:07 GMT Content-Type: text/html Content-Length: 178 Location: http://example.com/new-foo.html Connection: keep-alive HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sun, 03 Mar 2024 10:23:07 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 185 Last-Modified: Sun, 03 Mar 2024 10:21:20 GMT Connection: keep-alive ETag: "65e44f20-b9" Accept-Ranges: bytes <!DOCTYPE html> <html> <head> <title>Page example.com/new-foo.html</title> </head> <body> <h1>File /var/www/example.com/new-foo.html</h1> </body> </html>
$ cat /var/log/nginx/error.log ...: *1 "^" matches "/old-foo.html", client: 192.168.110.2, server: example.com, request: "GET /old-foo.html HTTP/1.1", host: "example.com" ...: *1 rewritten redirect: "/new-foo.html", client: 192.168.110.2, server: example.com, request: "GET /old-foo.html HTTP/1.1", host: "example.com" $ cat /var/log/nginx/access.log 192.168.110.2 - - [03/Mar/2024:13:25:04 +0300] "GET /old-foo.html HTTP/1.1" 301 178 "-" "curl/8.2.1" 192.168.110.2 - - [03/Mar/2024:13:25:04 +0300] "GET /new-foo.html HTTP/1.1" 200 185 "-" "curl/8.2.1"
Четвертый пример
Очень похоже на предыдущий пример, только редиректы внутренние. Клиент запрашивает страницу old-foo.html
— а получает содержимое файла new-foo.html
. При этом с точки зрения клиента — он просматривает страницу http://example.com/old-foo.html
.
map $uri $redirect { default ""; "/old-foo.html" "/new-foo.html"; "/old-bar.html" "/new-bar.html"; "/old-baz.html" "/new-baz.html"; } server { listen 80; server_name example.com www.example.com; root /var/www/example.com/; index index.php index.html; if ($redirect) { rewrite ^ $redirect last; } location / { try_files $uri $uri/ =404; } # записываем в лог-файл перенаправления rewrite_log on; error_log /var/log/nginx/error.log notice; }
$ curl -i -L http://example.com/old-foo.html HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sun, 03 Mar 2024 10:34:13 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 185 Last-Modified: Sun, 03 Mar 2024 10:21:20 GMT Connection: keep-alive ETag: "65e44f20-b9" Accept-Ranges: bytes <!DOCTYPE html> <html> <head> <title>Page example.com/new-foo.html</title> </head> <body> <h1>File /var/www/example.com/new-foo.html</h1> </body> </html>
$ cat /var/log/nginx/error.log ...: *1 "^" matches "/old-foo.html", client: 192.168.110.2, server: example.com, request: "GET /old-foo.html HTTP/1.1", host: "example.com" ...: *1 rewritten data: "/new-foo.html", args: "", client: 192.168.110.2, server: example.com, request: "GET /old-foo.html HTTP/1.1", host: "example.com" $ cat /var/log/nginx/access.log 192.168.110.2 - - [03/Mar/2024:13:34:13 +0300] "GET /old-foo.html HTTP/1.1" 200 185 "-" "curl/8.2.1"
Пятый пример
Допустим, что раньше на сайте был простой каталог оборудования, но потом сделали интеренет-магазин на отдельным домене (этот домен обслуживается тем же веб-сервером — но мог быть и совсем другой веб-сервер). Но на сайте осталось много ссылок типа example.com/catalog
— нужно всех перенаправлять на shop.example.com
. При этом есть три страницы на сайте, аналоги которых есть в интернет-магазине — это about.html
, delivery.html
и discount.html
.
# файл конфигурации /etc/nginx/sites-available/example.com server { listen 80; server_name example.com www.example.com; root /var/www/example.com/; index index.php index.html; location / { try_files $uri $uri/ =404; } location /catalog { rewrite ^ http://shop.example.com/ permanent; } location ~* /catalog/(?about|delivery|discount)\.html { rewrite ^ http://shop.example.com/$page.php permanent; } # записываем в лог-файл перенаправления rewrite_log on; error_log /var/log/nginx/error.log notice; }
# файл конфигурации /etc/nginx/sites-available/shop.example.com server { listen 80; server_name shop.example.com; root /var/www/shop.example.com/; index index.php index.html; location / { try_files $uri $uri/ =404; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass 127.0.0.1:9001; } }
Запрашиваем страницу сайта http://example.com/catalog/foo
— будет выполнен внешний редирект с кодом 301 на главную страницу магазина http://shop.example.com/index.php
.
$ curl -i -L http://example.com/catalog/foo HTTP/1.1 301 Moved Permanently Server: nginx/1.18.0 (Ubuntu) Date: Sun, 03 Mar 2024 12:08:06 GMT Content-Type: text/html Content-Length: 178 Connection: keep-alive Location: http://shop.example.com/ HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sun, 03 Mar 2024 12:08:06 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive <!DOCTYPE html> <html> <head> <title>Index page shop.example.com</title> </head> <body> <h1>File /var/www/shop.example.com/index.php</h1> </body> </html>
Запрашиваем страницу сайта http://example.com/catalog/about.html
— будет выполнен внешний редирект с кодом 301 на страницу магазина http://shop.example.com/about.php
.
$ curl -i -L http://example.com/catalog/about.html HTTP/1.1 301 Moved Permanently Server: nginx/1.18.0 (Ubuntu) Date: Sun, 03 Mar 2024 12:01:34 GMT Content-Type: text/html Content-Length: 178 Connection: keep-alive Location: http://shop.example.com/about.php HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sun, 03 Mar 2024 12:01:34 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive <!DOCTYPE html> <html> <head> <title>About page shop.example.com</title> </head> <body> <h1>File /var/www/shop.example.com/about.php</h1> </body> </html>
Шестой пример
Усложним предыдущий пример. Допустим, остались еще ссылки на товары каталога, которые имеют вид example.com/catalog/product-123.html
— нужно их перенаправлять на shop.example.com/product.php?code=123
.
# файл конфигурации /etc/nginx/sites-available/example.com map $uri $redirect { "~*^/catalog/product-(?<code>[0-9]+)\.html" "http://shop.example.com/product.php?code=$code"; "~*^/catalog/(?<page>about|delivery|discount)\.html" "http://shop.example.com/$page.php"; "~*^/catalog" "http://shop.example.com/"; default ""; } server { listen 80; server_name example.com www.example.com; root /var/www/example.com/; index index.php index.html; if ($redirect) { rewrite ^ $redirect permanent; } location / { try_files $uri $uri/ =404; } # записываем в лог-файл перенаправления rewrite_log on; error_log /var/log/nginx/error.log notice; }
# файл конфигурации /etc/nginx/sites-available/shop.example.com server { listen 80; server_name shop.example.com; root /var/www/shop.example.com/; index index.php index.html; location / { try_files $uri $uri/ =404; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass 127.0.0.1:9001; } }
$ curl -i -L http://example.com/catalog/product-123.html HTTP/1.1 301 Moved Permanently Server: nginx/1.18.0 (Ubuntu) Date: Mon, 04 Mar 2024 09:05:49 GMT Content-Type: text/html Content-Length: 178 Connection: keep-alive Location: http://shop.example.com/product.php?code=123 HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Mon, 04 Mar 2024 09:05:49 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive <!DOCTYPE html> <html> <head> <title>Product page shop.example.com</title> </head> <body> <h1>File /var/www/shop.example.com/product.php, code 123</h1> </body> </html>
Флаги last и break
Оба флага останавливают выполнение текущего набора директив модуля ngx_http_rewrite
внутри location
. Флаг last
запускает поиск нового location
, соответствующего перезаписанному URI. Флаг break
завершает фазу перезаписи, выполняются директивы, не связанные с модулем перезаписи.
server { listen 80; server_name example.com www.example.com; root /var/www/example.com/; index index.php index.html; location / { add_header X-Debug "Location root" always; try_files $uri $uri/ =404; } location = /foo.html { add_header X-Debug "Location = /foo.html" always; # здесь сначала добавляем флаг last, потом флаг break rewrite ^/foo\.html$ /bar.html; rewrite ^/bar\.html$ /baz.html; try_files $uri $uri/ =404; } # записываем в лог-файл перенаправления rewrite_log on; error_log /var/log/nginx/error.log notice; }
Сначала посмотрим на результат запроса, когда директивы rewrite
не имеют флагов. Сначала выполняются обе директивы rewrite
, потом происходит поиск нового location
.
$ curl -i -L http://example.com/foo.html HTTP/1.1 200 OK .......... X-Debug: Location root <!DOCTYPE html> <html> <head> <title>Page example.com/baz.html</title> </head> <body> <h1>File /var/www/example.com/baz.html</h1> </body> </html>
Теперь для первой директивы rewrite
добавим флаг last
. Выполняется только первая директива, потом происходит поиск нового location
.
$ curl -i -L http://example.com/foo.html HTTP/1.1 200 OK .......... X-Debug: Location root <!DOCTYPE html> <html> <head> <title>Page example.com/bar.html</title> </head> <body> <h1>File /var/www/example.com/bar.html</h1> </body> </html>
Теперь для первой директивы rewrite
установим флаг break
. Выполняется только первая директива (на фазе перезаписи), потом выполняются директивы внутри текущего location
, не связанные с модулем перезаписи (на фазе генерации ответа).
$ curl -i -L http://example.com/foo.html HTTP/1.1 200 OK .......... X-Debug: Location = /foo.html <!DOCTYPE html> <html> <head> <title>Page example.com/bar.html</title> </head> <body> <h1>File /var/www/example.com/bar.html</h1> </body> </html>
Если директива rewrite
используется в контексте server
— флаги работают одинаково. То есть, прерывается выполнение текущего набора директив модуля ngx_http_rewrite
, после чего выполняется поиск подходящего location
.
Директива конфигурации return
Можно сказать, что директива return
— упрощенный вариант директивы rewrite
для внешних редиректов.
return 301|302|303|307 новый_адрес;
return 1xx|2xx|4xx|5xx ["сообщение"];
Если код ответа 302 (Moved Temporarily) — то его можно не указывать, тогда у директивы будет только один аргумент новый_адрес
. Нестандартный код 444 закрывает соединение без передачи заголовка ответа. Как частный случай, URL перенаправления может быть задан как URI, локальный для данного сервера.
Первый пример
Для сайта нужен редирект с www.example.com
на example.com
, редирект с http
на https
и редирект с /some/path/index.php
на /some/path/
.
server { listen 80; listen 443 ssl; server_name example.com www.example.com; root /var/www/example.com/; index index.php index.html; # редирект с http на https if ($scheme = "http") { return 301 https://example.com$request_uri; } # редирект с www.example.com на example.com if ($host = "www.example.com") { return 301 https://example.com$request_uri; } # редирект с /some/path/index.php на /some/path/ if ($request_uri ~* "^(.*)index\.(php|html)") { return 301 $1; } location / { try_files $uri $uri/ =404; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass 127.0.0.1:9000; } error_page 404 403 /error/404.html; error_page 500 502 503 504 /error/50x.html; ssl_certificate /etc/ssl/certs/example-com.crt; ssl_certificate_key /etc/ssl/private/example-com.key; access_log /var/log/nginx/example.com-access.log; error_log /var/log/nginx/example.com-error.log error; }
При запросе http://www.example.com/
— выполняется редирект на https://www.example.com/
$ curl -i -L --ssl-no-revoke http://www.example.com/ HTTP/1.1 301 Moved Permanently Server: nginx/1.18.0 (Ubuntu) Date: Sat, 09 Mar 2024 12:10:28 GMT Content-Type: text/html Content-Length: 178 Connection: keep-alive Location: https://example.com/ HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sat, 09 Mar 2024 12:10:28 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 169 Last-Modified: Mon, 26 Feb 2024 12:30:36 GMT Connection: keep-alive ETag: "65dc846c-a9" Accept-Ranges: bytes <!DOCTYPE html> <html> <head> <title>Index page example.com</title> </head> <body> <h1>Index page example.com, file /var/www/example.com/index.html</h1> </body> </html>
При запросе http://www.example.com/index.html
— сначала выполняется редирект на https://example.com/index.html
, потом еще один редирект на https://example.com/
.
$ curl -i -L --ssl-no-revoke http://www.example.com/index.html HTTP/1.1 301 Moved Permanently Server: nginx/1.18.0 (Ubuntu) Date: Sat, 09 Mar 2024 12:11:44 GMT Content-Type: text/html Content-Length: 178 Connection: keep-alive Location: https://example.com/index.html HTTP/1.1 301 Moved Permanently Server: nginx/1.18.0 (Ubuntu) Date: Sat, 09 Mar 2024 12:11:44 GMT Content-Type: text/html Content-Length: 178 Location: https://example.com/ Connection: keep-alive HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sat, 09 Mar 2024 12:11:44 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 169 Last-Modified: Mon, 26 Feb 2024 12:30:36 GMT Connection: keep-alive ETag: "65dc846c-a9" Accept-Ranges: bytes <!DOCTYPE html> <html> <head> <title>Index page example.com</title> </head> <body> <h1>Index page example.com, file /var/www/example.com/index.html</h1> </body> </html>
Второй пример
Допустим, что раньше на сайте был простой каталог оборудования, но потом сделали интеренет-магазин на отдельным домене (этот домен обслуживается тем же веб-сервером — но мог быть и совсем другой веб-сервер). На сайте осталось много ссылок на товары каталога, которые имеют вид example.com/catalog/product-123.html
— нужно их перенаправлять на shop.example.com/product.php?code=123
. Есть еще три страницы, аналоги которых есть в интернет-магазине — это about.html
, delivery.html
и discount.html
.
# файл конфигурации /etc/nginx/sites-available/example.com map $uri $redirect { "~*^/catalog/product-(?<code>[0-9]+)\.html" "/product.php?code=$code"; "~*^/catalog/(?<page>about|delivery|discount)\.html" "/$page.php"; "~*^/catalog" "/"; default ""; } server { listen 80; server_name example.com www.example.com; root /var/www/example.com/; index index.php index.html; if ($redirect) { return 301 http://shop.example.com$redirect; } location / { try_files $uri $uri/ =404; } }
# файл конфигурации /etc/nginx/sites-available/shop.example.com server { listen 80; server_name shop.example.com; root /var/www/shop.example.com/; index index.php index.html; location / { try_files $uri $uri/ =404; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass 127.0.0.1:9001; } }
$ curl -i -L http://example.com/catalog/product-123.html HTTP/1.1 301 Moved Permanently Server: nginx/1.18.0 (Ubuntu) Date: Sat, 09 Mar 2024 11:55:59 GMT Content-Type: text/html Content-Length: 178 Connection: keep-alive Location: http://shop.example.com/product.php?code=123 HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Sat, 09 Mar 2024 11:55:59 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive <!DOCTYPE html> <html> <head> <title>Product page shop.example.com</title> </head> <body> <h1>File /var/www/shop.example.com/product.php, code 123</h1> </body> </html>
Поиск: CLI • Nginx • Конфигурация • Настройка • Установка • FastCGI • Сервер