NGINX для чайников
Чаще всего когда мне надо сделать так, чтоб какой-то URL вызывал какой-то PHP код, приходится иметь дело с конфигом nginx.
И каждый раз это превращается в долгие страдания и заканчивается громкими матами полным отчаянием.
Даже однажды разобравшись в том как это делается, всё равно успеваешь всё забыть до того как это понадобится в следующий раз. И опять всё сначала. Кстати, помимо nginx есть еще несколько подобных контр-интуитивных вещей, которые никак не удаётся “выучить раз и навсегда”: например, язык bash, или регулярные выражения. Про bash есть даже шутка:
https://twitter.com/jakewharton/status/1334177665356587008
Поэтому я решил очень кратко законспектировать основные правила, по которым nginx разруливает запросы, чтоб в следующий раз иметь быстрое напоминание, без необходимости перечитывать документацию. Тут поверхностно описаны только самые часто-употребляемые директивы, так что на звание “руководства” не претендую.
http
Итак. Конфигурация состоит из вложенных друг в друга “контекстов”, отделенных символами {
и }
. Директивы распространяются на контекст в котором они объявлены и на все вложенные в него, при этом вложенные контексты могут переопределять эти директивы - примерно как одноименные переменные во вложенной функции в javascript перекрывают переменные из более “внешней” функции. Всё начинается в так называемом “главном контексте” - это всё что снаружи фигурных скобок. Этот контекст нам не очень интересен, там задаются какие-то дефолтные настройки. Интересен вложенный в него контекст http
, а в нем - контекст(ы) server
.
server
Один контекст server
содержит директивы для одного “виртуального сервера”, и таких серверов может быть множество. При поступлении HTTP-запроса, nginx определяет соответствующий виртуальный сервер по запрашиваемому IP-адресу, порту и содержимому заголовка Host
. Пример:
# "Главный" контекст
http {
# Какие-то общие настройки по-умолчанию,
# могут быть переопределены во вложенных контекстах
client_max_body_size 100M;
# Виртуальные сервера:
# 1. этот конфиг будет применен при запросах на порт 80
# с заголовком Host: mysite1.com или Host: mysite2.com
server {
listen 80;
server_name mysite1.com mysite2.com;
}
# 2. этот конфиг будет применен при запросах на порт 80
# с заголовком Host:another-mysite.com
server {
listen 80;
server_name another-mysite.com;
}
# 3. а этот - при запросах на порт 80
# со всеми другими значениями заголовка Host или вообще без него.
server {
# конфигурация по-умолчанию для запросов на порт 80
listen 80 default_server;
}
# ВАЖНО и неочевидно: если Host не совпал ни с одним из server_name,
# и default_server для порта не указан явно,
# будет применен конфиг ПЕРВОГО виртуального сервера для этого порта.
# То есть, при конфиге такого вида:
server {
listen 192.168.1.17:81;
server_name mysite1.com;
}
server {
listen 192.168.1.1:81;
server_name mysite2.com;
}
# запрос к 192.168.1.1:81 c заголовком Host: anothersite.com будет обработан
# первым виртуальным сервером, несмотря на несовпедение Host с server_name
}
Директива server_name
может содержать регулярные выражения или переменные, подробнее тут: http://nginx.org/ru/docs/http/ngx_http_core_module.html#server_name и тут: http://nginx.org/ru/docs/http/request_processing.html.
location
Внутри каждого контекста server
может быть несколько контекстов location
, которые определяют правила для разных запрашиваемых URI. Сравнивается только URI-часть запроса, после её URL-раскодирования и разрешения относительных путей (.
и ..
), без GET-параметров. Подробности в примере:
server {
listen 80 default_server;
# Общие настройки для всего виртуального сервера,
# могут быть переопределены во вложенных контекстах
# Корневая папка, в которой сервер будет искать файлы,
# может быть переопределена в location'ах
root /var/www/mysite;
# Конфигурации для конкретных URI:
# 1. точное соответсвие (=)
location = /exact-uri {
return 200 "Requested URI equals '/exact-uri'";
}
# 2. соответствие по префиксу (/)
location /prefix {
return 200 "Requested URI starts with '/prefix'";
}
# 3. регистрозависимое соответствие регулярному выражению (~)
location ~ ^regexp$ {
return 200 "Requested URI matches '^regexp$'";
}
# 4. регистроНЕзависимое соответствие регулярному выражению (~*)
location ~* ^regexp$ {
return 200 "Requested URI matches '^regexp$' or '^REGEXP$'";
}
}
Вкратце алгоритм поиска соответствия выглядит так:
если найдено точное совпадение с
location
‘ом точного соответствия, применяется эта конфигурация и поиск останавливается.иначе находится самый длинный из префиксных
location
‘ов совпадающий с URI и запоминается;ищется совпадение среди
location
‘ов заданных регулярным выражением, и применяется первое совпадение;если же ни одно из регулярных выражений не совпало с URI, применяется префиксный
location
, запомненный ранее на шаге 2.
Если мы хотим остановить поиск на шаге 2 и не искать по регулярным выражениям, можно добавить к префиксному location
‘у знак ^~
, вот так:
location ^~ /prefix {
# если URI начинается с /prefix, применится этот блок и поиск будет остановлен
}
location ~ /prefix/.* {
# это не будет применено, поиск остановится в предыдущем блоке
}
location
‘ы можно вкладывать друг в друга, при этом вложенные должны указывать абсолютные, а не относительные URI:
location /outer {
# абсолютные URI - префикс /outer дублируется во всех вложенных определениях!
location /outer/inner {
return 200 "Requested URI starts with '/outer/inner'";
}
location ~ ^/outer/regexp.* {
return 200 "Requested URI matches '^/outer/regexp.*'";
}
}
Подробнее можно почитать тут: http://nginx.org/ru/docs/http/ngx_http_core_module.html#location
index
Чаще всего используются директивы index
и try_files
.
Они могут находиться как на уровне server
, так и на уровне location
.
index
указывает какой файл отдавать по умолчанию если в запрошенном URI не указан конкретный файл, то есть если URI выглядит как путь к папке, а не к файлу. Указанные файлы проверяются в порядке слева направо и, как только находится существующий файл, происходит внутренний редирект на URI с добавлением имени этого фала.
Внутренний редирект означает исполнение всего блока server
заново, с измененным URI. Это не то же самое что HTTP-редирект, т.к. URI видоизменяется только “внутри” nginx’a и клиент этого не видит. То есть:
server {
# ...
# при запросе www.mysite.com/some/long/path, если существует файл
# /var/www/mysite_root/some/long/path/existing-file.html,
# в ответ будет получено 'hello'
root /var/www/mysite_root;
index
not-existing-file.html # этого файла не существует в папке some/long/path
existing-file.html # а этот существует в папке some/long/path
another-existing-file.html; # и этот тоже, но это уже не важно)
# это не применится, потому что несуществующие файлы пропускаются
location /not-existing-file.html {
return 200 ':(';
}
# это применится, потому что файл существует и внутренний редирект происходит
location /existing-file.html {
return 200 'hello';
}
}
При этом внутренний редирект происходит только один раз:
server {
# ...
location / {
# если файл i1.html существует,
# добавлеяем /i1.html к URI и ищем подходящий location заново
index i1.html;
}
location /i1.html {
# даже если файл i2.html существует, второй редирект не происходит (!),
# т.к. у нас уже есть конкретный файл для ответа - i1.html.
index i2.html;
}
location /i2.html {
return 200 ':('; # это не выполнится
}
}
Для PHP приложений с единой точкой входа (как в большинстве современных фреймворков) достаточно указать на уровне виртуального сервера:
# направлять все запросы без указания конкретного файла на index.php
index index.php;
location ~ index.php$ {
# проксируем запрос на PHP-FPM...
}
Документация: http://nginx.org/ru/docs/http/ngx_http_index_module.html.
try_files
Директива try_files
позволяет последовательно проверить несколько файлов и отдать первый существующий, а также указать fallback на случай если ни один из файлов не найден. Чаще всего она используется в сочетании с переменной $uri
, которая содержит текущий URI с учетом всех внутренних преобразований (см. index
и rewrite
). Пример:
server {
#...
root /var/www;
location ~ \.jpg$ {
# при запросе www.mysite.com/path/to/image.jpg
# будет проверено существование файла /var/www/path/to/image.jpg,
# затем, если такого нет, будет проверен /var/www/path/to/images/image.jpg,
# если и его нет, будет внутренний редирект на /fallback-uri.html
try_files $uri /images/$uri /fallback-image.jpg;
}
location /fallback-image.jpg$ {
# это выполнится если запрошенный файл не существует
# ни в запрошенной директории ни в поддиректории images
return 200 'default image';
# кстати, если нужно чтоб этот location не был доступен извне,
# а только для внутренних редиректов, добавляем директиву internal
internal;
}
# в качестве fallback можно указать код ошибки, чаще всего 404
location ~ \.html$ {
try_files $uri =404;
}
# ...или named location
location / {
try_files $uri @phpfpm;
}
location @phpfpm {
# проксируем запрос на PHP-FPM...
}
}
В try_files
можно указать $uri/
(с слешом в конце), тогда nginx будет искать запрашиваемую директорию. По умолчанию, nginx не отдает содержимое директорий (если только не указано autoindex on
), поэтому в случае если директория существует, в дело вступает директива index
, указывающая какой файл из этой директории отдавать (см. выше).
server {
#...
root /var/www;
index index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ index.html$ {
return 200 'serving file $uri/index.thml';
}
}
try_files
не сохраняет GET-параметры текущего запроса при внутреннем редиректе. Чтоб избежать их потери используется конструкция из переменных $is_args
и $args
:
server {
# ...
try_files $uri $uri/ /index.php$is_args$args;
# или так, если хотим добавить новые пареметры и сохранить текущие
try_files $uri $uri/ /index.php?additional_param=1&$args;
location = index.php {
# тут $args будет содержать все изначальные и добавленные GET-параметры
}
}
Документация: http://nginx.org/ru/docs/http/ngx_http_core_module.html#try_files.
Внутренние редиректы могут быть результатом и других директив, например rewrite
или error_page
.
rewrite
Эта директива делает внутренний или внешний редирект - если новый URI начинается с http://
, https://
, или переменной $scheme
, то произойдет внешний (HTTP) редирект, иначе - внутренний. Если новый URI содержит GET-параметры, они будут добавлены к тем что уже есть в текущем запросе. Подробнее в документации: http://nginx.org/ru/docs/http/ngx_http_rewrite_module.html#rewrite. Пример:
server {
# ...
location = /rewritten {
rewrite .* /another-location?additional_param=1; # внутренний редирект
}
location = /another-location {
rewrite .* 'http://mysite.com/another-location; # а теперь HTTP-редирект
}
}
error_page
Указывает URI, на который следует сделать внутренний редирект, если в процессе обработки запроса возникла ошибка. Детали в документации: http://nginx.org/ru/docs/http/ngx_http_core_module.html#error_page. Пример:
server {
# ...
# заменять все 404 коды на 200 и редиректить на другой location
error_page 404 =200 /404_error.html;
location = /404_error.html {
# этот блок будет выполнен если в процессе возникнет ошибка с кодом 404
}
}
error_page
, как и try_files
, не сохраняет GET-параметры текущего запроса при внутреннем редиректе. Так что для их сохранения нужно явно добавлять переменную $args
к новому URI (см. выше).
Количество внутренних редиректов ограничено 10-ю за один HTTP-запрос, во избежание бесконечных циклов. Если происходит больше 10-ти, nginx вернёт ошибку.
Директивы location
и rewrite
могут содержать регулярные выражения з захватывающими группами, значения из которых могут быть использованы в последующих директивах:
server {
# ...
rewrite /user-(\d+)/product-(\d+) /user/$1/product/$2;
location /new/user/(\d+)/product/(\d+) {
return 200 'you requested user with id $1 and product with id $2';
}
}
Также можно использовать встроенные переменные: http://nginx.org/en/docs/http/ngx_http_core_module.html#variables.
alias
Еще одна полезная директива - alias
. Она позволяет заменить часть URI, например чтобы обратиться к файлам, чей путь в файловой системе не совпадает с URI. Например:
server {
# ...
root /var/www/mysiteroot;
location /styles {
# файлы из папки /var/www/mysiteroot/web/assets/css
# будут доступны по URI /styles/{имя файла}
alias /web/assets/css;
}
location /scripts {
# файлы из папки /var/www/mysiteroot/web/assets/js
# будут доступны по URI /scripts/{имя файла}
alias /web/assets/js;
}
}
alias
удобно совмещать с location
‘ами, заданными регулярными выражениями:
server {
# ...
root /var/www/mysiteroot;
location /admin/(.*) {
# "маппинг" базового URI на другую корневую директорию
# (в данном случае на /var/www/mysiteroot/protected/web)
alias /protected/web/$1;
}
}
Документация: http://nginx.org/ru/docs/http/ngx_http_core_module.html#alias.
На этом всё. С помощью рассмотренных директив можно решить большинство простых задач, хотя это, наверное, одна сотая часть возможностей nginx’a. Хорошо что остальные возможности вряд ли пригодятся рядовому PHP-разработчику : )