Правило маршрутизации / 01
Кто выиграл
location?
Если несколько блоков подходят к URI, порядок выбора важнее того, как правила выглядят в файле.
Сначала exact match, затем самый длинный prefix. Если у prefix нет ^~, NGINX проверяет regex по порядку и использует первое совпадение.
Ситуация
Запрет есть.
Но он не выбран.
Префиксный блок закрывает каталог, а regex-блок передаёт PHP-запрос приложению. Запрос /private/export.php совпадает с обоими правилами. Если prefix не отмечен как ^~, совпавший regex может стать итоговым location.
location /private/ {
deny all;
}
location ~ \.php$ {
proxy_pass http://app:9000;
}Что меняет ^~
Получен HTTP 200; mock upstream принял /private/export.php.
200 / upstream hitПолучен HTTP 403; второе обращение не появилось в журнале mock upstream.
403 / no upstreamПроверено на loopback с NGINX 1.30.6 и mock upstream вместо app:9000. Это воспроизводимый локальный тест, не production-сервис и не замена проверке прав в backend.
КЛЮЧ Читай приоритет, а не только порядок строк. Для regex важен порядок самих regex-блоков.
Интерактивная трасса / модель
Запусти запрос.
Посмотри, кто победил.
Меняй сценарий, Host и URI. Линия показывает путь, а результат объясняет выбранное правило.
Это ограниченные учебные примеры, а не полноценный парсер nginx.conf. Перед исправлением сверяйся с документацией и тестируй конфигурацию.
Выбери точку сбоя
- listenадрес и порт
- serverвиртуальный хост
- locationвыбранный URI
- handlerфайл или proxy
- appпроверка прав
Развилка / location
один URI · два кандидатаlocation /private/совпал, но уступилdeny alllocation ~ \\.php$выбранный блокapp:9000Обычный prefix совпал, но регулярное выражение для .php тоже совпадает. Без ^~ NGINX проверяет regex после выбора самого длинного prefix. В этой конфигурации regex location может стать победителем, поэтому deny all из prefix-блока не применяется.
Если весь /private/ должен оставаться закрытым, сделайте prefix ^~ и проверьте точный набор правил: location ^~ /private/ { deny all; }
Исправление
Закрепи
границу.
Если весь путь /private/ должен оставаться закрытым, ^~ говорит NGINX не проверять regex после выбора этого prefix.
location ^~ /private/ {
deny all;
}
location ~ \.php$ {
proxy_pass http://app:9000;
}nginx -tcurl -i -H "Host: api.demo.test" http://127.0.0.1:8080/private/export.phpВ примере порт 8080. Подставь свой listen, затем проверь статус, тело ответа и журнал приложения.
Даже правильно закрытый NGINX-маршрут не заменяет проверку пользователя и прав в backend. Защищённый объект должен проверяться на каждом действии приложения.
OWASP Authorization Cheat Sheet ↗Snippets
пересекли границу.
В историческом advisory Kubernetes Security Response Committee описал риск ingress-nginx: пользователь с правом создавать или менять Ingress-объекты мог злоупотребить custom snippets и получить Kubernetes Secrets из разных namespace в затронутой конфигурации.
Доступ к секретам кластера при недостаточном разделении прав в multi-tenant среде.
Отключить snippets для недоверенных авторов, ограничить RBAC и обновляться по актуальным advisory и поддерживаемым выпускам.
Это риск контроллера ingress-nginx и его annotations, а не ошибка выбора обычного location в standalone NGINX.