1. 1
  2. 2
  3. 3

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

Шесть решений над каждым запросом

Коротко

Это главный урок раздела: всё остальное в курсе - подробности к нему.

Если цепочка решений уже в голове - листай до «Теперь сам».

На каком шаге сломалось

Симптом с боевого сервера: на домене admin.shop.local вместо админки открывается главная страница магазина. Конфиги обоих сайтов на месте, оба nginx -t проходят.

Прежде чем читать дальше - на каком этапе обработки это могло произойти и сколько таких этапов вообще бывает?

Ответ на второй вопрос: шесть. Ответ на первый - на самом первом, и в конце урока станет понятно, почему остальные пять можно даже не проверять.

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

Посылка в сортировочном центре проходит фиксированный набор станций, и порядок станций не зависит от того, что написано на коробке.

Сначала определяют город - по индексу, и только по нему. Потом внутри города определяют участок. Потом смотрят, не надо ли вернуть отправителю - если надо, дальше посылка не едет вообще. Потом решают, откуда её брать: она уже на складе или её надо получить у поставщика. Потом клеят наклейки. И в самом конце записывают в журнал, что и когда отправили.

Никакая надпись на коробке не заставит наклеить наклейку раньше, чем определён город. Ровно так же ни одна директива в конфиге не сработает раньше своего решения.

Механизм: шесть решений

Список короткий, и его стоит держать в голове целиком. Слева - вопрос, справа - то, из чего nginx берёт на него ответ.

1 какой server 2 какой location 3 не выйти ли 4 откуда контент 5 заголовки 6 запись в лог по listen и Host по правилам приоритета return, rewrite - дальше не идём файл с диска ИЛИ proxy_pass add_header, gzip, expires код и размер уже известны
Цепочка одна и та же для любого запроса и любого конфига. Диагностика идёт по ней сверху вниз.

Правило

Любую поломку «отдаёт не то» разбирай, проходя список сверху вниз, и останавливайся на первом решении, которое дало не тот результат. Проверять пятое, не проверив первое, - потерянное время: если запрос попал не в тот server, всё остальное в конфиге отношения к делу не имеет.

Разбор: живой запрос по шагам

Клиент делает GET /api/cart с заголовком Host: shop.local, соединение пришло на порт 80.

Решение 1 - кто отвечает. nginx смотрит на все server-блоки, которые слушают 80-й порт, и ищет среди них тот, чей server_name совпадает с shop.local. Не нашёл - берёт default_server, а если и его нет - первый блок на этом порту. Приложение тут ни при чём: выбор идёт только по порту и заголовку Host.

Решение 2 - какой location. Внутри выбранного блока сравниваются пути. У /api/cart может быть несколько кандидатов: location /, location /api/ и какая-нибудь регуляркаРазбирается в разделе 4, глава «Как выбирается location»: Пять форм записи location - шаблон вроде ~ \.php$, под который подходит целое семейство адресов. Победит не первый и не самый очевидный, а тот, кого выберут довольно неинтуитивные правила приоритета - им посвящён целый раздел.

Решение 3 - не закончить ли прямо здесь. Если в выбранном блоке есть return 301 ..., дальше не происходит ничего: клиент получает редирект. Ни файлов, ни бэкендов.

Решение 4 - откуда брать содержимое. Развилка на две ветки: либо путь превращается в путь на диске (root, alias, try_files), либо запрос уходит на бэкенд (proxy_pass). Одновременно и то и другое не бывает.

Решение 5 - чем обвесить ответ. Заголовки, сжатие, кеш-директивы.

Решение 6 - что записать в лог. Строка появляется ПОСЛЕ того, как ответ отправлен, - поэтому в ней уже известны и код ответаКод ответаПервая цифра говорит, что делать дальше: 2 - готово, 3 - иди по другому адресу, 4 - запрос выполнить нельзя, 5 - сломалось на сервере. Остальные две уточняют., и размер, и время.

Разбор: почему server_name не фильтрует

Наивное правило звучит так: «в блоке написано server_name shop.local, значит на другие имена он не ответит». Вот конфиг, на котором это неверно.

/etc/nginx/conf.d/shop.local.confhttp
server {
    listen 80;
    server_name shop.local;
    root /var/www/shop;
}

Запрос с чужим именем:

$  команда
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: evil.example' http://127.0.0.1/
↳  выводcurl -s -o /dev/null -w '%{http_code}\n' -H 'Host: evil.example' http://127.0.0.1/
200

Двести. Магазин ответил на имя, которого не знает.

Причина - в решении 1: nginx обязан выбрать какой-то блок из тех, что слушают порт. Он ищет совпадение по имени, не находит, ищет default_server, не находит и берёт первый блок на этом порту. Отказаться отвечать он не может: на порту кто-то должен ответить.

Отсюда и объяснение симптома из начала урока. Если админка объявлена в файле, который не попал в собранный конфиг (лежит в sites-available без ссылки, назван .conf.bak), то запрос к admin.shop.local не находит своего блока и достаётся магазину. Проверять при этом location, root и права на файлы - бесполезно: сломалось на первом решении.

Правильный способ не отвечать на чужие имена - завести блок-заглушку, который станет умолчанием:

/etc/nginx/conf.d/00-default.confhttp
server {
    listen 80 default_server;
    server_name _;
    return 444;
}

Код 444 - особый: nginx закрывает соединение, не отвечая вовсе. Подробности про выбор блока и про default_server - в разделе про то, кто отвечает.

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

«Открывается чужой сайт». Решение 1: имя не совпало, ответил блок по умолчанию или первый на порту.

«Отдаётся не тот файл» или «404 на существующий файл». Решение 2 или 4: выбран другой location либо путь на диске собрался не так, как ожидалось.

«Внезапный редирект в цикл». Решение 3: сработал return или rewrite, до содержимого дело не дошло.

«Пропал заголовок безопасности». Решение 5, и почти всегда это наследование add_header - отдельная ловушка следующего раздела.

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

Это единственная схема курса, которую стоит помнить наизусть. Она работает даже с чужим конфигом на пятьсот строк, потому что порядок решений не зависит от того, как конфиг написан, - а значит, разбор всегда начинается в одном и том же месте.

Практически это выглядит так: вместо чтения конфига сверху вниз задаёшь шесть вопросов по очереди и на первом же несовпадении получаешь ответ. Разница по времени - десятки минут на каждом инциденте.

Теперь сам

На сервере два сайта. Запрос к https://shop.local/static/app.css возвращает 301 на https://shop.local/. Файл на диске есть, права верные, location /static/ в конфиге объявлен. На каком решении сломалось и что смотреть?

Ответ. На третьем. Права и наличие файла относятся к решению 4, но до него дело не дошло: раз пришёл 301, сработал return или rewrite. Смотреть надо две вещи: return на уровне самого server (он выполняется до выбора location и бьёт всё) и rewrite внутри выбранного блока. Проверка - nginx -T | grep -n "return\|rewrite" плюс rewrite_log on с уровнем notice в error_log, который покажет, какая именно строка сработала. Смотреть права на файл в этой ситуации бессмысленно: цепочка оборвалась раньше.

Главное

При отладке веди конкретный запрос по цепочке решений и ищи первый неверный результат. Схема служит начальной картой: return на уровне server может завершить запрос до выбора location, а внутренний переход меняет URI и запускает выбор location заново.

Комментарии

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

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

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