Nginx. Установка и настройка. Часть 3 из 3

02.02.2024

Теги: CLIFastCGINginxКонфигурацияНастройкаСерверУстановка

Переменные в файле конфигурации

В конфигурационном файле 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 запроса на уровне location
  • NGX_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 по новому URI
  • break — завершает обработку текущего набора директив модуля 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 • Сервер

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.