Almost every confusing NGINX config traces back to one assumption: that the blocks are tried in the order you wrote them. They are not, and the gap between that assumption and the real algorithm is where the afternoons go.
The order that actually runs
- Exact match. A
location = /paththat matches ends the search immediately. Nothing else is considered. - The prefix round. Every prefix location is checked and the longest match is remembered. Not the first one in the file, the longest one.
- The early exit. If that longest prefix carries
^~, it wins now and neither regular expressions nor predicate locations are tried. - Regular expressions, tried in the order they appear in the file, and the first one that matches wins, beating the remembered prefix.
- Predicate locations, from nginx 1.31.5: if no regular expression matched, every
location $variableis tried in file order, and the first whose variable is neither empty nor0wins, again beating the remembered prefix (ngx_http_core_module, location). - Fallback. If nothing in steps four and five matched, the remembered longest prefix wins after all.
File order decides only steps four and five. Everything else is about specificity and modifiers. A config with no predicate locations behaves exactly as it did before 1.31.5, which is why older material describes the same walk in five steps.
The match is made against the path alone: locations test the URI without the arguments after ?, so a query string never decides an exact, prefix or regex round, and only reaches the decision through a predicate on a variable such as $arg_name.
The consequence that catches people
Consider a config with a /images/ prefix block and, somewhere further down, location ~ \.(gif|jpg|png)$.
A request for /images/logo.gif does not use the /images/ block. The prefix round remembers it, then step four runs the regular expressions, the extension matches, and the regex wins. The more specific-looking block, written first, loses to a general one written later.
This is why "my /images/ block is being ignored" is nearly always a file-extension regex somewhere below it. The block is not being ignored; it is being outranked by design.
What ^~ actually means
It is read as "higher priority", and that is the wrong mental model. ^~ means stop before the regular expressions. It does not make the prefix stronger, it removes steps four and five from the search.
Which makes it the precise fix for the case above. Change the block to location ^~ /images/ and the extension regex never runs for URIs under /images/, while continuing to work everywhere else.
Longest, not first
Within the prefix round, /a/b/c/ beats /a/ for a request to /a/b/c/d regardless of which appears first in the file. Ordering prefix blocks for readability is safe; ordering regex blocks changes behaviour.
What to check when a request lands in the wrong place
Is there an exact match? It would have ended the search at step one.
Is there a regex that matches? If so, and no ^~ fired, it beat every prefix. Look below the block you expected, not above it.
Is the prefix you expected actually the longest match? A longer one elsewhere in the file takes the round.
Is there a predicate location? On nginx 1.31.5 or later, a location $variable whose value is neither empty nor 0 beats the prefix whenever no regex matched first.
Are two blocks identical? NGINX rejects a duplicate exact or prefix location outright; a duplicate regex is simply unreachable.
What a learner should be able to state cold
NGINX tries exact matches first and stops if one hits; then remembers the longest prefix match, not the first; then, unless that prefix carried ^~, tries regular expressions in file order and lets the first match win over the prefix; on nginx 1.31.5 or later then tries predicate locations in file order, where the first variable that is neither empty nor 0 wins; and falls back to the prefix only if neither round matched. File order matters for regular expressions and predicates and nothing else. ^~ is an early exit rather than a priority boost, and it is the fix when a regex keeps stealing traffic from a prefix block.
Sources
- NGINX documentation, ngx_http_core_module, location (read 2026-10-26): the selection order,
^~stopping before regular expressions and predicate locations, the exact-match exit, and the predicate rule (a variable that is neither empty nor "0") - NGINX documentation, How nginx processes a request (read 2026-10-26): locations of all types test only the URI part of the request line, without arguments
- NGINX changes log, nginx 1.31.5, 02 Sep 2026 (read 2026-10-26): "Feature: predicate locations"