Nginx выбирает location по строгому алгоритму: сначала точное совпадение с модификатором =, затем самый длинный префикс, потом регулярные выражения в порядке их следования в конфигурации, и только в конце обычный префиксный location. Если найден префикс с модификатором ^~, проверка регулярных выражений прекращается. Это правило определяет, какой блок обработает каждый входящий запрос, и именно его нарушение приводит к неожиданным редиректам, потере производительности и ошибкам маршрутизации.
Разберем алгоритм по шагам, покажем типичные конфликты и дадим рабочие конфигурации, которые можно применять сразу. Материал ориентирован на DevOps-инженеров и системных администраторов, которые хотят проектировать предсказуемую маршрутизацию без сюрпризов в продакшене.
Как Nginx выбирает location: алгоритм в деталях
При получении запроса Nginx выполняет пять последовательных шагов. Сначала он ищет точное совпадение URI с location, объявленным через модификатор =. Если такое совпадение найдено, поиск немедленно завершается, и используется именно этот блок. Точное совпадение имеет абсолютный приоритет над всеми остальными типами location.
Если точного совпадения нет, Nginx просматривает все префиксные location и запоминает самый длинный подходящий. При этом он отдельно отмечает, есть ли у этого префикса модификатор ^~. Если есть, проверка регулярных выражений пропускается, и выбранный префиксный location обрабатывает запрос.
Когда самый длинный префикс не имеет модификатора ^~, Nginx переходит к проверке регулярных выражений. Они проверяются строго в порядке их появления в конфигурационном файле. Первое совпавшее регулярное выражение побеждает и немедленно завершает поиск. Если ни одно регулярное выражение не совпало, используется запомненный ранее самый длинный префиксный location.
Точное совпадение (=) - наивысший приоритет
Модификатор = задает точное совпадение URI. Он полезен для конкретных путей, которые не должны обрабатываться другими правилами. Например, страница входа чаще всего находится по адресу /login, и для нее можно задать отдельный location:
location = /login {
proxy_pass http://auth_service;
}
Запрос /login будет обработан именно этим блоком, даже если в конфигурации есть префиксный location / или регулярное выражение, подходящее под этот URI. Точное совпадение немедленно завершает поиск, поэтому его стоит использовать для критичных страниц: healthcheck-ов, точек аутентификации, служебных эндпоинтов.
Префикс с ^~ - приоритет над регулярными выражениями
Модификатор ^~ применяется к префиксному location и означает: если этот префикс выбран как самый длинный, регулярные выражения не проверяются. Это ключевой инструмент для защиты статических каталогов от перехвата динамическими правилами.
location ^~ /static/ {
root /var/www;
}
Если в конфигурации есть регулярное выражение location ~ \.(css|js)$, оно не будет применено к запросу /static/style.css, потому что префикс ^~ /static/ выбран как самый длинный и блокирует проверку регулярных выражений. Это поведение критично для производительности: статические файлы не должны проходить через цепочку регулярных выражений.
Регулярные выражения (~ и ~*) - порядок имеет значение
Регулярные location объявляются с модификатором ~ для учета регистра и ~* без учета регистра. Порядок их следования в конфигурации определяет приоритет: первое совпадение останавливает поиск. Это отличает регулярные выражения от префиксных location, где выбирается самый длинный, а не первый.
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
location ~* \.(jpg|png|gif)$ {
root /var/www/images;
}
Запрос /index.php совпадет с первым регулярным выражением, и обработка завершится. Запрос /photo.JPG совпадет со вторым, поскольку модификатор ~* игнорирует регистр. Если поменять эти блоки местами, поведение для PHP-файлов не изменится, но для изображений может измениться, если шаблоны пересекаются.
Обычный префикс - запасной вариант
Обычный префиксный location без модификаторов используется только в двух случаях: когда нет точного совпадения, нет префикса с ^~ и ни одно регулярное выражение не совпало. Самый длинный подходящий префикс запоминается на раннем этапе, но его применение откладывается до завершения проверки регулярных выражений.
location /api {
proxy_pass http://backend;
}
Запрос /api/v1/users будет обработан этим location, если в конфигурации нет более специфичных правил. Обычный префикс хорошо подходит для корневых путей и fallback-сценариев, но его не стоит использовать для путей, которые могут пересекаться с регулярными выражениями.
Типичные конфликты location и их разрешение
Конфликты возникают, когда несколько location потенциально подходят под один URI, но разработчик ожидает иного поведения. Разберем четыре сценария, которые чаще всего встречаются в реальных конфигурациях.
Префикс /static/ против регулярного выражения \.(css|js)$
Классическая ошибка: разработчик объявляет префиксный location для статики и отдельное регулярное выражение для CSS и JavaScript файлов. Без модификатора ^~ регулярное выражение перехватывает запросы, предназначенные для статического каталога.
location /static/ {
root /var/www;
}
location ~ \.(css|js)$ {
proxy_pass http://asset_server;
}
Запрос /static/style.css будет обработан регулярным выражением, а не префиксом, потому что регулярные выражения проверяются после запоминания префикса, но до его применения. Решение: добавить ^~ к префиксному location:
location ^~ /static/ {
root /var/www;
}
Теперь запросы к статике гарантированно обрабатываются без проверки регулярных выражений. Это устраняет конфликт и снижает нагрузку на CPU, поскольку регулярные выражения не выполняются для каждого статического файла.
Несколько регулярных выражений: кто победит?
Когда несколько регулярных выражений подходят под один URI, побеждает первое по порядку в конфигурации. Это отличается от префиксных location, где выбирается самый длинный. Рассмотрим пример:
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
location ~* \.(php|html)$ {
proxy_pass http://legacy_server;
}
Запрос /index.php совпадет с обоими регулярными выражениями, но обработан будет первым, потому что оно стоит раньше. Второе регулярное выражение никогда не получит PHP-файлы, если первое совпадает. Рекомендация: располагать более специфичные шаблоны раньше, а более общие позже. Если нужно, чтобы HTML-файлы обрабатывались legacy-сервером, а PHP-файлы через FastCGI, порядок должен быть именно таким, как в примере.
Точное совпадение против префикса: всегда выигрывает =
Точное совпадение имеет приоритет над любым префиксным location, даже если префикс длиннее. Это правило часто упускают из виду при проектировании конфигурации.
location = /admin {
return 301 /admin/;
}
location /admin/ {
proxy_pass http://admin_panel;
}
Запрос /admin без завершающего слэша будет обработан точным совпадением, которое выполняет редирект на /admin/. Запрос /admin/users совпадет с префиксным location и будет проксирован. Такая схема решает проблему дублирования страниц с разными URL и обеспечивает корректную обработку как корневого пути, так и вложенных.
^~ против регулярного выражения: блокировка проверки
Модификатор ^~ отменяет проверку регулярных выражений только если этот префикс выбран как самый длинный. Если есть более длинный префикс без ^~, регулярные выражения все равно будут проверены.
location ^~ /static/ {
root /var/www;
}
location /static/js/ {
proxy_pass http://js_builder;
}
location ~ \.js$ {
proxy_pass http://cdn;
}
Запрос /static/js/app.js: самый длинный префикс - /static/js/, он не имеет ^~, поэтому Nginx переходит к проверке регулярных выражений. Регулярное выражение \.js$ совпадает и обрабатывает запрос. Префикс ^~ /static/ не блокирует проверку, потому что не он выбран как самый длинный. Чтобы гарантировать обработку статики без регулярных выражений, нужно добавить ^~ и к более длинному префиксу:
location ^~ /static/js/ {
proxy_pass http://js_builder;
}
Практические рекомендации по организации location
Правильная организация location снижает риск конфликтов и делает конфигурацию читаемой. Следующие практики проверены на реальных проектах и помогают избежать большинства проблем с маршрутизацией.
Используйте ^~ для статических ресурсов
Статические файлы не должны проходить через цепочку регулярных выражений. Каждый запрос к изображению, CSS или JavaScript файлу, который обрабатывается регулярным выражением, потребляет CPU и увеличивает задержку. Модификатор ^~ гарантирует, что статика обрабатывается немедленно после выбора префикса.
location ^~ /static/ {
root /var/www;
expires 30d;
add_header Cache-Control "public, immutable";
}
Этот блок обрабатывает все запросы к /static/ без проверки регулярных выражений. Производительность растет, поведение становится предсказуемым. Для проектов с большим количеством статических ресурсов это обязательная практика.
Порядок регулярных выражений: от частного к общему
Регулярные выражения проверяются в порядке их следования, поэтому более специфичные шаблоны должны стоять раньше. Если общий шаблон стоит первым, он перехватит запросы, предназначенные для более специфичного обработчика.
# Сначала специфичные шаблоны
location ~ ^/api/v2/.*\.json$ {
proxy_pass http://api_v2;
}
location ~ \.json$ {
proxy_pass http://api_v1;
}
Запрос /api/v2/users.json совпадет с первым регулярным выражением и будет обработан API v2. Если поменять блоки местами, запрос уйдет на API v1, что приведет к ошибке. Правило простое: чем точнее шаблон, тем выше он должен быть в конфигурации.
Точные совпадения для критичных страниц
Для служебных эндпоинтов, которые не должны обрабатываться другими правилами, используйте модификатор =. Это гарантирует, что запрос не попадет в общий fallback и не будет перехвачен регулярным выражением.
location = /healthcheck {
access_log off;
return 200 "OK";
}
Запрос /healthcheck всегда возвращает 200 без обращения к бэкенду и без записи в access-лог. Это полезно для систем мониторинга и балансировщиков нагрузки, которые проверяют доступность сервера. Точное совпадение исключает любые сюрпризы.
Отладка проблем с location
Когда location работает не так, как ожидается, нужны инструменты для диагностики. Проверка синтаксиса и подробное логирование позволяют быстро локализовать проблему.
Проверка синтаксиса и логирование
Первым шагом всегда выполняйте проверку конфигурации:
nginx -t
Команда показывает синтаксические ошибки и предупреждения до перезагрузки Nginx. После проверки включите debug-лог для отслеживания выбора location:
error_log /var/log/nginx/debug.log debug;
Debug-лог содержит записи о том, какой location был выбран для каждого запроса, какие регулярные выражения проверялись и почему было принято то или иное решение. Для продакшена debug-лог следует включать только на время диагностики, поскольку он создает большую нагрузку на диск.
Тестирование с помощью curl
Чтобы быстро проверить, какой location обрабатывает конкретный URI, добавьте кастомный заголовок в каждый location и отправьте запрос через curl:
location = /login {
add_header X-Location-Matched "exact-login";
proxy_pass http://auth_service;
}
location ^~ /static/ {
add_header X-Location-Matched "static-prefix";
root /var/www;
}
Теперь запрос с curl покажет, какой блок сработал:
curl -I https://example.com/login
В заголовках ответа будет строка X-Location-Matched: exact-login. Это простой и надежный способ проверить маршрутизацию без чтения debug-логов. После завершения отладки кастомные заголовки можно удалить или оставить для внутренней диагностики.
Более глубокие методы диагностики маршрутизации, включая анализ access-логов и работу с модулем echo, описаны в руководстве по отладке маршрутизации Nginx. Если вы проектируете маршрутизацию для микросервисов, обратите внимание на конфигурации location, proxy_pass и rewrite для микросервисной архитектуры. Для комплексного подхода к маршрутизации на уровне приложений изучите практическое руководство по Nginx как L7-маршрутизатору.
Если вы разворачиваете Nginx на облачной инфраструктуре, Timeweb Cloud предоставляет готовые серверы и базы данных с гибким масштабированием для веб-проектов. Для работы с API и автоматизации задач маршрутизации можно использовать AiTunnel, агрегатор доступа к более чем 200 моделям нейросетей с оплатой в рублях.