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аналогично директивеbreakredirect— возвращает временное перенаправление с кодом 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 • Сервер