# теория · шаг 1 из 4
Что наследуется, а что нет
Коротко
Директива из внешнего блока действует и во внутренних, пока её не переопределят. Побеждает ближайшее значение. Исключений два, и оба кусаются.
- Обычные директивы (
root,alias,gzip,expires) наследуются и перекрываются по одной - как ожидаешь. - Директивы-списки (
add_header,proxy_set_header,limit_req) не дополняются, а замещаются целиком: один такой во вложенном блоке отменяет весь список сверху. - Обработчик содержимого не наследуется вовсе. Вложенный
locationвнутри location сproxy_passНЕ проксирует.
Если правило «ближайший побеждает» и два исключения знакомы - листай до «Теперь сам».
Сначала ответь сам
Есть работающий прокси на приложение. Понадобилось пометить статику заголовком - добавили вложенный блок:
location /app/ {
proxy_pass http://127.0.0.1:8080;
location /app/static/ {
add_header X-Cached "yes" always;
}
}
proxy_pass передаёт запрос приложению (подробно - в разделе про
проксированиеРазбирается в разделе 7, глава «Слеш в proxy_pass»: Один символ, который меняет путь): 127.0.0.1 - адрес этого же
компьютера, 8080 - порт, на котором слушает приложение.
Что вернёт запрос GET /app/static/logo.png?
Инстинктивный ответ - «картинку от приложения, только с новым заголовком». Настоящий - 404. Приложение этого запроса не увидит вообще.
На что это похоже
Наследование в nginx похоже на дресс-код в компании.
Общее правило записано в уставе: «деловой стиль». Отдел может уточнить: «у нас
по пятницам свободная форма» - и внутри отдела действует уточнение, а всё
остальное берётся из устава. Ровно так работают обычные директивы: пишешь
root в server, переопределяешь в одном location, в остальных действует
исходный.
А теперь представь правило, устроенное иначе: «список допустимых цветов». Отдел пишет свой список из одного цвета - и общий список не дополняется, а отменяется целиком. Никто не предупреждает; просто с этого дня в отделе разрешён один цвет вместо десяти.
Это и есть директивы-списки, и главная беда с ними в том, что выглядят они как все остальные.
Механизм: три способа собрать значение
При сборке конфига nginx для каждого уровня вычисляет итоговое значение каждой директивы. Способов у него три, и директива выбирает свой сама.
Обычный случай запоминать не надо - он работает как ждёшь:
server {
root /var/www/shop;
location /downloads/ {
root /mnt/big-disk; # тут корень другой, ближайший побеждает
}
}
Запрос /downloads/a.zip nginx ищет в /mnt/big-disk/downloads/a.zip:
rootРазбирается в разделе 5, глава «root против alias»: Как URI превращается в путь приклеивает к корню весь путь запроса.
Так же ведёт себя alias: замер показал, что location, вложенный в location с
alias, берёт файлы по тому же alias.
Правило
Директива наследуется вниз, пока её не переопределят на более внутреннем уровне. Исключений два: список замещается целиком, обработчик содержимого не наследуется.
Разбор: почему вложенный location дал 404
Вернёмся к конфигу из начала урока. Чтобы повторить опыт, приложение не
нужно: его роль сыграет сам nginx, ещё двумя server-блоками. Первый печатает
адрес, который получил, второй - заголовки X-A и X-B, которые пригодятся
ниже:
server {
listen 8080;
return 200 "BACKEND $request_uri\n";
}
server {
listen 8081;
return 200 "a=$http_x_a b=$http_x_b\n";
}
return 200 "…" отвечает кодом 200 и этим текстом (подробнее о
returnРазбирается в разделе 13, глава «return против rewrite»: return: коды, тела, редиректы), $http_x_a - значение заголовка запроса
X-A. Проверено на nginx 1.31.5, ответы настоящие.
BACKEND /app/x
404
Первый запрос попал в location /app/ и ушёл на бэкенд. Второй попал во
вложенный location /app/static/, а там proxy_pass нет: он не наследуется.
Обработчика у location не осталось, и nginx сделал то, что делает по умолчанию, -
пошёл искать файл на диске. Файла нет - 404.
Обработчик содержимого - директива, которая сама решает, чем ответить:
proxy_pass, fastcgi_pass, uwsgi_pass, grpc_pass. Без неё nginx ищет
файл на диске.
Если файл на диске случайно есть, всё ещё интереснее: запрос отдаст его, минуя
приложение. Именно так утекают исходники и .env, положенные рядом.
Ровно то же самое происходит с try_filesРазбирается в разделе 5, глава «try_files и SPA»: Кандидаты и назначение - директивой
«попробуй эти файлы по очереди»: вложенный location её не
унаследует, и правило «если файла нет - отдай index.html» перестанет
действовать на этой ветке адресов.
Разбор: где наивное правило подводит
Наивное правило: «раз директива написана выше, она действует ниже». Для списков
это неверно, и заметить подмену по виду конфига нельзя. Вот проверка на
proxy_set_header:
server {
proxy_set_header X-A "from-server";
location /only/ { proxy_pass http://127.0.0.1:8081; }
location /both/ {
proxy_set_header X-B "from-loc";
proxy_pass http://127.0.0.1:8081;
}
}
Бэкенд на порту 8081 печатает то, что получил (&& запускает вторую команду,
если первая прошла успешно):
a=from-server b=
a= b=from-loc
В /both/ заголовок X-A исчез. Одна добавленная строка отменила все
proxy_set_header уровнем выше - в том числе те, которыми бэкенду сообщают
настоящий адрес клиента и схему запросаРазбирается в разделе 7, глава «Заголовки: Host, X-Real-IP, X-Forwarded-*»: Что бэкенд перестаёт видеть. Приложение начнёт видеть всех
пользователей с адреса прокси, и виновата будет строка, которая про адреса
ничего не говорила.
Список директив-списков стоит помнить наизусть, он короткий:
add_header, add_trailer, proxy_set_header (и его родня
fastcgi_param, uwsgi_param), limit_req, limit_conn, access_log,
error_page.
Что ломается без этого
- 404 или чужой файл там, где раньше был бэкенд. Ищи вложенный
location- скорее всего, его добавили ради одной мелочи. - Бэкенд перестал видеть настоящий адрес клиента. Ищи
proxy_set_header, добавленный в location. - Заголовки безопасности пропали на части сайта. Ищи
add_headerв location - об этом отдельный урок, следующий. - Запросы перестали попадать в лог.
access_logтоже список: указал его в одном location - потерял назначения, заданные выше.
Зачем это в работе
Оба исключения объединяет одно: их включает добавление строки, а не удаление. Конфиг работал, кто-то дописал одну безобидную директиву - и сломалось что-то соседнее, о чём эта директива не говорила ни слова.
Отсюда практика ревью: правку конфига читают не по диффу, а по вопросу «какой
блок она затрагивает целиком». Появился новый location внутри старого -
проверь, был ли у родителя обработчик. Появился один add_header или
proxy_set_header - проверь, что было в списке выше.
И проверяй опытом, а не памятью: curl -I до правки и после видит разницу
быстрее, чем глаз.
Теперь сам
1. В server стоит root /var/www/shop;, а в location /files/ -
alias /mnt/storage/;. Что вернёт запрос /files/report.pdf?
Файл /mnt/storage/report.pdf. alias в location перекрывает root сервера,
как перекрыл бы его другой root: ближайший побеждает. Отличается сборка
пути - alias заменяет совпавшую частьРазбирается в разделе 5, глава «root против alias»: alias: замена вместо приклеивания /files/
целиком, а root приклеил бы весь путь.
2. В server два add_header, в location /api/ один proxy_set_header
и proxy_pass. Какие заголовки увидит клиент в ответе на /api/cart?
Оба серверных. proxy_set_header - список заголовков к бэкенду, а
add_header - список заголовков клиенту. Это разные списки, и вытесняют
они каждый свой. Ловушка срабатывает только между однотипными директивами.
3. Как добавить заголовок к статике, не сломав проксирование из первого примера урока?
Не заводить вложенный location: отобрать статику отдельным location того же
уровня, со своим root или со своим proxy_pass. Из /app/ и /app/static/
для /app/static/logo.png nginx выберет второй - у него префикс длиннее
(правила выбораРазбирается в разделе 4, глава «Как выбирается location»: Две фазы выбора). Либо, если статику всё же
отдаёт приложение, поставить add_header в существующий location /app/ и
не создавать вложенности вовсе.
Главное
Директивы наследуются вниз, побеждает ближайшее переопределение - кроме двух случаев: список замещается целиком, а обработчик содержимого не наследуется. Оба включаются добавлением строки, а не удалением, и ломают соседнее.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий