Алгоритм выбора location в Nginx

January 4th 2022

Частный случай с одним уровнем вложенности


Если Вы новичок в nginx, то следует рассмотреть вначале частный случай без использования вложненных location, т. к. алгоритм для частного случая значительно проще:

  1. Вначале будет искаться равенство (=). Оно имеет высший приоритет.
  2. Потом будет искаться максимальный по длине префиксный location ((   ) или (^~)), после чего будет проверено, есть ли на найденном location модификатор приоритета (^~), и если он есть, то будет возвращён этот location.
  3. Потом будут проверяться регулярные выражения ((~) и (~*)) сверху вниз. При совпадении будет возвращён первый location из них.
  4. Потом вернётся тот префиксный location, который мы нашли до этого.

Обратите внимание, что этот алгоритм не применим при наличии вложенных location.

Общий случай с вложенными location


  1. Стартуем с верхнего уровня.
  2. Если на текущем уровне выполняется равенство (=), поиск прекращается — это и будет результат, т. к. такой location не может иметь никаких других вложенных location.
  3. В противном случае ищем на текущем уровне самый большой префиксный location ((   ) или (^~)).
    • Если такой префиксный location существует, то делаем его текущим уровнем и переходим к п. 2.
    • В противном случае выходим из цикла.
  4. Мы вышли из цикла. На данный момент мы нашли «самый большой» префиксный location, но не думайте, что это самый большой из всех. Пример:

    location /abc {
    	location /abcdefghi {
    		…
    	}
    }
    
    location /abcdef {
    	…
    }
    

    В данном примере мы перейдём в /abcdef, т. к. на его уровне он переборол более короткий /abc. Но по факту существуют location и больше него.
  5. Теперь в найденном location мы ищем первый верный regexp. При нахождении поиск полностью прекращается. Обратите внимание: в этом пункте мы по факту ищем regexp на самом нижнем уровне, а не на верхнем, как многие могли бы подумать. Т. е. поиск regexp идёт снизу, а не сверху (но внутри одного уровня идёт сверху, а не снизу).
    • Далее, если ничего не найдено, поднимаемся на один уровень вверх и аналогично ищем первый regexp, но в этот раз уже только при условии, что location, в котором мы были до этого, не имел метки (^~). Повторяем этот пункт до тех пор, пока подниматься будет некуда.
    • При этом нужно иметь ввиду:
      • Даже если какой-то из уровней имеет метку (^~), это не значит, что мы не осуществляем подъём. Подъём осуществляется всегда, но если более нижний уровень имел метку (^~), то на текущем уровне поиск regexp'ов не проводится.
      • Возможности запретить проверку regexp в самом нижнем уровне нет — для этого нужно создать ещё один вложенный уровень. А вот запретить проверку regexp на нулевом уровне можно — для этого location первого уровня (который находится на нулевом уровне) должен иметь метку (^~).
  6. Мы сделали подъём по дереву, но так и не нашли ни одного regexp. Раз regexp не найден, возвращаем «почти самый большой» префиксным location, который был найден ранее. Готово.

Также при этом:


Пример уязвимого конфига


location ~ \.php$ {
	deny all; # Здесь должно быть проксирование на php-fpm
}

location /posts/ {
	location ~ (.*)_2x(\.[a-z]+)$ {
		try_files $uri $1$2 =404;
	}
}

В данном конфиге мы настроили игнорирование "_2x", если файл не найден. Например, nginx попробует найти файл /posts/img/a_2x.png как по указанному пути, так и по пути /posts/img/a.png. Но в реальности, если мы запросим /posts/authData_2x.php, то мы получим исходный текст скрипта authData.php в голом виде. Чтобы избежать таких ошибок, нужно знать, как обрабатывается location в nginx.

Также дополнительной защитой может являться хранение скриптов в отдельной директории, недоступной из под обычных location. В этом случае, если наш location на php по каким-то причинам не сработает, пользователь получит ошибку 404, а не исходный текст скрипта.

Перенаправление location


  1. Если try_files не содержит кода ошибки последним параметром, то будет сделано перенаправление в другой location, т. к. последний параметр всегда делает перенаправление. Обратите внимание: код ошибки в try_files должен писаться через равно (=).
  2. index и error_page при срабатывании всегда делают перенаправление в другой location. Также перенаправление делает rewrite, если добавить в него флаг last.

Другое


  1. При выборе location не учитывается строка запроса, которая начинается со знака "?".