Магия location: приоритеты и модификаторы
location выбирается НЕ по порядку в файле, а по строгому алгоритму приоритетов. Незнание этого — источник самых злых багов конфигурации.
«Длиннейший префикс побеждает независимо от позиции. Регэкспы — в порядке файла. Запомни это — и location перестанет быть магией.»
Внутри server обычно несколько блоков location. Когда приходит запрос, Nginx должен выбрать ровно один. Делает он это по чёткому алгоритму, а не «сверху вниз», как многие думают.
Модификаторы location
location = /path— точное совпадение. Высший приоритет, мгновенный выбор.location ^~ /path— префикс с приоритетом: если совпал, регэкспы дальше не проверяются.location ~ /pattern— регулярка (с учётом регистра).location ~* /pattern— регулярка без учёта регистра.location /path— обычный префикс (без модификатора).
Алгоритм выбора
Запрос URI
|
v
1. Есть точный "= URI" ? --да--> ВЗЯТЬ ЕГО, СТОП
| нет
v
2. Найти ДЛИННЕЙШИЙ префикс среди обычных и ^~
|
+-- он с "^~" ? --да--> ВЗЯТЬ ЕГО, СТОП
| нет (запомнить как запасной)
v
3. Проверять регэкспы ~ / ~* В ПОРЯДКЕ ФАЙЛА
|
+-- первый совпавший --> ВЗЯТЬ ЕГО, СТОП
| ни один не совпал
v
4. Взять запомненный длиннейший префикс из шага 2
Ключевые мысли: точное — первым; префиксы сравниваются по длине (длиннейший выигрывает) независимо от позиции в файле; регэкспы проверяются по порядку и первый совпавший побеждает. ^~ отменяет проверку регэкспов, если префикс совпал.
Смоделируем матчинг префиксов на Python
Сердце алгоритма — выбор длиннейшего совпавшего префикса. Реализуем именно эту часть:
prefixes = ["/", "/images/", "/images/icons/", "/api/"]
def longest_prefix_match(uri, prefixes):
best = None
for p in prefixes:
if uri.startswith(p) and (best is None or len(p) > len(best)):
best = p # длиннее -> приоритетнее
return best
for uri in ["/images/icons/star.png", "/images/cat.png", "/api/users", "/about"]:
print(f"{uri:30} -> location {longest_prefix_match(uri, prefixes)}")
Попробуй сам ▶ Заметь: /images/icons/star.png уходит в более длинный /images/icons/, а не в /images/ — хотя оба подходят. Именно так Nginx и рассуждает на шаге 2.
Как работает под капотом
Nginx строит из префиксных location дерево для быстрого поиска длиннейшего совпадения, а регэкспы хранит отдельным списком в порядке объявления. Поэтому добавление сотни префиксов почти не замедляет выбор, а вот десятки регэкспов проверяются линейно — их стоит держать в разумном числе и ставить самые частые выше.
Частые ошибки
- Думать, что location проверяются сверху вниз. Префиксы — по длине, не по порядку. Это разрушает много «логичных» предположений.
- Регэксп перехватывает то, что должно было уйти в префикс. Например,
~* \.php$может перехватить запрос, который ты хотел отдать как статику. Используй^~для защиты статики. - Слишком общий регэксп вверху списка съедает всё, что под ним.
Best practices
- Защищай каталоги статики через
^~:location ^~ /static/ { ... }— чтобы регэкспы их не перехватили. - Точное совпадение
= /для главной страницы экономит проверки. - Держи регэкспы минимальными и упорядочивай от частых к редким.
Разбор реального набора location
Чтобы алгоритм отложился в голове, разберём типичный сервер с четырьмя блоками: = / (точное совпадение главной), ^~ /static/ (статика), ~* \.php$ (PHP-скрипты) и / (всё остальное на бэкенд). Запрос на / мгновенно уйдёт в точное совпадение. Запрос на /static/app.css поймает приоритетный префикс ^~ /static/, и регулярка про .php уже не будет проверяться — даже если бы в имени файла мелькало php. Запрос на /index.php не имеет длинного префикса, поэтому дойдёт до регулярки и уйдёт обработчику PHP. А /about провалится сквозь всё и попадёт в запасной /.
Главная ловушка здесь — взаимодействие статики и регулярных выражений. Очень частая ошибка новичков: настроить обработку PHP через location ~ \.php$ и не защитить статику приоритетным префиксом. Тогда хитрый запрос может проскочить в PHP-обработчик и попытаться выполнить файл, который должен был просто отдаться как картинка. Поэтому правило-мнемоника: каталоги статики всегда закрывай через ^~, чтобы регулярки физически не могли их перехватить. Помни и про производительность: префиксы Nginx ищет по дереву очень быстро, а регулярки проверяет линейно одну за другой — десятки regex-локаций замедляют выбор, так что держи их число небольшим и ставь самые частые выше по файлу.
Итоги
Выбор location: точное = первым, затем длиннейший префикс (с ^~ — окончательно), затем регэкспы по порядку файла, и в конце — запасной длиннейший префикс. Порядок в файле важен только для регэкспов. Освоив это, ты избавишься от целого класса загадочных багов. Дальше — оптимизация отдачи статики на полную скорость.