1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6

# теория · шаг 1 из 6

Фазы: кто и когда решает

Коротко

Запрос идёт по nginx не сверху вниз по конфигу, а по фазам, и порядок фаз задан жёстко: server rewrite → выбор location → rewrite → лимиты → доступ → try_files → контент → фильтры.

  • return срабатывает раньше deny, а return в server {} - раньше выбора location.
  • rewrite ... last повторяет выбор location, break сохраняет блок. Число внутренних переходов ограничено десятью.
  • По порядку записи выполняются только return, rewrite, set, break и if; остальное - настройки уровня.

Если это и так знакомо - листай до «Теперь сам» в конце и проверь себя на конфиге.

Что получит этот запрос

Прежде чем читать дальше, ответь себе. Конфиг ниже выглядит совершенно однозначно. Что вернётся на запрос к /admin/ с чужого адреса?

/etc/nginx/conf.d/shop.local.confhttp › server
location /admin/ {
    deny all;
    return 200 "тут ничего нет\n";
}

Не 403. Клиент получит 200 и текст.

Если ответ показался очевидным и неправильным - хорошо, это самое полезное место урока. Дальше разбираемся, почему так, и почему это не баг nginx.

На что это похоже

Конфиг выглядит как список команд, и читается как список команд. На самом деле это анкета, которую по очереди читают несколько разных служб.

Представь приёмную, где твоё заявление идёт по окнам в фиксированном порядке. Первое окно - «перенаправление»: там могут отправить в другой отдел или сразу выдать готовый ответ и закрыть дело. Второе - «пропуск»: там смотрят, положено ли тебе вообще сюда. Третье - «выдача».

На одном листе можно написать и «выдать справку», и «этого не пускать». Но лист идёт по окнам, и первое окно закончит дело раньше, чем второе успеет на него посмотреть. Резолюция «не пускать» останется неисполненной - не потому, что её кто-то отменил, а потому что до второго окна лист не доехал.

return живёт в первом окне. deny - во втором.

Механизм: конвейер фаз

nginx обрабатывает запрос восемью фазами. Каждая - отдельный этап со своим набором модулей, и попасть на этап раньше своей очереди директива не может.

server rewrite выбор location rewrite preaccess access try_files content filters return / rewrite / set / if поиск по URI то же, но внутри location limit_req, limit_conn allow / deny, auth_basic есть ли файл на диске root, proxy_pass add_header, expires, gzip last: до 10 кругов
Директива не может сработать раньше своей фазы, как бы высоко она ни стояла в файле. Смена URI отправляет запрос назад, к выбору location.

Правило

Три формулировки, которые стоит унести дословно - из них выводится почти любой спорный случай.

return в location бьёт всё, что ниже по конвейеру. Он в фазе rewrite - раньше лимитов, раньше проверки доступа, раньше обращения к диску.

Если выполнение дошло до return в server, ответ завершится до выбора location. Но предшествующий rewrite ... last может закончить эту фазу раньше: учитывай порядок директив rewrite.

По порядку записи выполняются только директивы модуля rewrite: return, rewrite, set, break и блоки if. Прерывают этот порядок return (запрос закончен), break (хватит переписывать, работаем в этом же location) и флаг last (URI сменился, ищем location заново).

Разбор: почему 200, а не 403

Пройдём тот самый конфиг по фазам.

/etc/nginx/conf.d/shop.local.confhttp › server
location /admin/ {
    deny all;
    return 200 "тут ничего нет\n";
}
  1. server rewrite - в server {} директив модуля rewrite нет, фаза пуста.
  2. выбор location - URI /admin/ попадает в location /admin/.
  3. rewrite - здесь есть return 200. Он выполняется, ответ сформирован, обработка запроса закончена.
  4. Фазы preaccess, access, try_files, content не выполняются вовсе. deny all не «отменён» и не «проигнорирован» - до него не дошла очередь.

Клиент получает 200. Заметь, что порядок строк в файле роли не сыграл: если поменять deny и return местами, результат тот же.

Как надо, если цель - закрыть админку:

/etc/nginx/conf.d/shop.local.confhttp › server
location /admin/ {
    deny all;                    # фаза access, и в этом location больше ничего
}

location = /admin/zaglushka {
    return 200 "тут ничего нет\n";
}

Заглушка уехала в отдельный location, и теперь deny доживает до своей фазы.

Разбор: где ломается наивное правило

Из первого разбора легко унести упрощённое «что написано выше в файле, то и сработает». Вот конфиг, на котором это правило даёт неверный ответ.

/etc/nginx/conf.d/shop.local.confhttp › server
limit_req_zone $binary_remote_addr zone=api:10m rate=1r/s;   # ограничитель частоты, раздел про лимиты

location /old-api/ {
    rewrite ^/old-api/(.*)$ /api/$1 last;   # (.*) захватывает хвост в $1
}

location /api/ {
    limit_req zone=api burst=5;
    proxy_pass http://app:8000;
}

Вопрос: к какому лимиту относится запрос на /old-api/cart?

Наивный ответ - «ни к какому, в /old-api/ лимита нет». Правильный - к лимиту /api/. Фаза rewrite идёт раньше preaccess, флаг last отправляет запрос назад к выбору location, и на фазу лимитов запрос приходит уже с URI /api/cart, то есть из другого location.

То же самое работает и в обратную сторону, и это чаще кусается: запрос, переписанный ИЗ защищённого location в незащищённый, лимит потеряет.

Что ломается без этого

Три строки, которые приводят в лог и в чужой конфиг. За каждой стоит именно порядок фаз.

403 не наступает, хотя deny написан. Рядом в том же location стоит return - разобрано выше. Симптом коварен тем, что curl отдаёт содержимое, и проверка «страница открывается» проходит как успешная.

location /assets/ никогда не срабатывает. Смотри выше по файлу: в том же server {} стоит return 301. Он в первой фазе, location выбирается во второй, и до второй дело не доходит:

/etc/nginx/conf.d/shop.local.confhttp
server {
    listen 80;
    server_name shop.local;
    return 301 https://shop.local$request_uri;

    location /assets/ {
        root /var/www/shop;      # никогда не сработает
    }
}

500 и rewrite or internal redirection cycle в error_log. Круг «сменили URI - ищем location заново» повторился одиннадцать раз. Тот же счётчик тратят try_files с последним аргументом-URI, error_page и именованные location - поэтому конфиг, где try_files ведёт в location, который снова делает try_files, упирается в предел незаметно, до первого запроса по неудачному пути. Строка в логе выглядит как проблема бэкенда, хотя бэкенд ни при чём.

Зачем это в работе

Типовая задача с ревью: «закрыть админку по адресу». Человек дописывает deny all и рядом вежливую заглушку return 200, проверяет curl - страница отвечает, значит конфиг жив, - и отправляет в прод. Дыра уезжает вместе с коммитом, и найдут её не тестом, а по чужому запросу в логах.

Второй случай - разбор инцидента ночью. В логе 500 и rewrite or internal redirection cycle, дежурный идёт смотреть бэкенд, потому что 500 «обычно оттуда». Знание про десять кругов экономит здесь не аккуратность, а час ночного времени.

Как посмотреть, что происходит на самом деле

nginx -T печатает собранный конфиг целиком - со всеми include, в том виде, в каком nginx прочтёт его с диска. А rewrite_log on; вместе с error_log на уровне notice пишет каждую сработавшую перезапись:

$  команда
rewrite_log on;   # пишет в error_log на уровне notice (ниже warn) - при error_log warn не видно

Строки вида "^/shop/(.*)$" matches "/shop/socks", client: ... избавляют от гадания, какая именно регулярка сработала и в каком порядке.

Теперь сам

Что получит запрос GET /cart с адреса 203.0.113.10?

/etc/nginx/conf.d/shop.local.confhttp › server
location /cart {
    allow 192.168.1.0/24;   # /24 - подсеть из 256 адресов 192.168.1.*
    deny all;
    rewrite ^/cart$ /korzina last;
}

location /korzina {
    return 200 "корзина\n";
}

Ответ: 200 и текст «корзина». Фаза rewrite идёт раньше access, поэтому rewrite ... last выполняется первым, URI меняется на /korzina, и поиск location начинается заново. Запрос приходит в location /korzina, где никаких allow/deny нет, - проверка доступа из первого location до него так и не добралась. Чтобы ограничение работало, deny должен стоять в том location, куда запрос в итоге приезжает, либо перезапись должна идти без last, с break.

Главное

Определи фазу каждой директивы, когда конфиг ведёт себя неожиданно. return и rewrite могут завершить или перенаправить запрос до проверки доступа. Порядок строк важен внутри фазы rewrite, но перестановка deny выше return не меняет порядок выполнения самих фаз.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.