Skip to content

Nginx Location Tester — Why That Block Wins

See which nginx location block wins — and why every other block lost. Free location match tester for =, ^~, ~ and ~*, running entirely in your browser.

No Tracking Runs in Browser Free
Your configuration is parsed locally in your browser and never uploaded. Server configs carry upstream hostnames and auth blocks, so open the Network panel and watch it stay silent — or go offline entirely.
Try a real misconfiguration
Selected location
~* \.(gif|jpg|jpeg)$

First regex in configuration order that matched. Length is irrelevant.

What nginx actually matches
Request target
/documents/1.jpg
Normalised $uri
/documents/1.jpg
Query string $args

Matching runs against the normalised path only. The query string is split off first and never participates.

Why that block won
Why that block won
Line location Stage Outcome Reason
2 = / Exact No match An = location needs the whole URI to be equal, not to start with it.
3 / Prefix Matched but shorter It matches, but another prefix matched more characters.
4 /documents/ Prefix Matched but shorter It matches, but another prefix matched more characters.
5 ^~ /images/ Prefix No match The URI does not start with this prefix.
6 ~* \.(gif|jpg|jpeg)$ Regex Selected First regex in configuration order that matched. Length is irrelevant.

nginx location Modifiers: =, ^~, ~, ~* Compared

nginx location Modifiers: =, ^~, ~, ~* Compared
Modifier Syntax Matches by Order Stops regex Typical use
= location = /path Equality 1 Yes Hot paths such as / — the fastest possible match.
^~ location ^~ /path Starts with 2 Yes Directories that must never be handed to a regex, such as uploads.
~ location ~ regex PCRE regex 3 No Extension routing where case matters.
~* location ~* regex PCRE regex 3 No Extension routing where case does not matter.
(none) location /path Starts with 4 No General routing by path.
@ location @name Internal only No error_page and try_files fallbacks.

The order column is not a simple ranking. A prefix location only stops regex evaluation when it carries ^~ and is itself the longest match — which is exactly why a ^~ block sometimes appears to do nothing.

nginx location Priority: The Resolution Order

nginx location Priority: The Resolution Order
# Stage Ends the search What happens
1 Normalise No Percent-decoding, . and .. resolution, repeated slashes collapsed. The query string is split off here and never takes part in matching.
2 Exact Yes location = /path, compared for equality. A hit ends the search immediately.
3 Auto-redirect Yes A proxying location whose name is the URI plus a slash. nginx answers 301 and never reaches the regexes.
4 Prefix No Every matching prefix is compared and the longest is remembered. Configuration order is ignored.
5 Nested No The winning prefix is descended into. A nested regex is tried before the parent level.
6 Regex Yes Regexes are tried in configuration order and the first match wins. A longer or more specific regex written later never runs.
7 Fallback Yes No regex matched, so the prefix remembered earlier is used.
Matching order, the ^~ short-circuit, nested-location descent, auto-redirect behaviour and URI normalisation were checked against nginx's own source and confirmed by running the configurations on nginx 1.27.5. — Go Tools Engineering · Jul 22, 2026

The selection rules on this page were verified against a running nginx 1.27.5 rather than taken from secondary write-ups, and the engine is covered by unit tests derived from those runs.

nginx location Matching: Quick Answers

Does nginx process regex locations before prefix matches?

No — prefix locations are checked first, but a matching regex still wins. nginx checks all prefix locations first and remembers the longest match, then evaluates regex locations in the order they appear in the file. The first matching regex wins. If no regex matches, the remembered prefix location is used. The one exception is ^~: when the longest matching prefix carries it, the regex phase is skipped entirely.

Does the order of location blocks matter in nginx?

Only for regex locations. Prefix locations — including = and ^~ — are selected by longest match, so their order in the file is irrelevant. Regex locations are tested top to bottom and the first match wins, so moving a regex block changes which one runs. A more specific regex placed below a broader one never executes.

What does the ^~ modifier actually do?

It stops nginx after the prefix match instead of raising priority. If the longest matching prefix location carries ^~, nginx skips the regex phase and uses that block. It does not make the prefix match longer or rank it above other prefixes — it only suppresses regex evaluation. That is why a ^~ block appears to do nothing when a longer plain prefix also matches: the longer one is remembered instead, and it does not suppress anything.

Is location /static the same as location /static/?

No — /static also matches /staticfoo. Prefix matching is a plain string comparison, not a path-segment boundary, so location /static serves /staticfiles and /static-backup as well. Separately, when /static/ is used with proxy_pass, a request to /static without the trailing slash gets a 301 redirect to /static/ before any regex is considered.

Does nginx decode %2F and collapse double slashes before matching?

Yes — matching runs against the normalised URI, not the raw request. nginx decodes %XX, resolves . and .. and compresses repeated slashes before selecting a location. A decoded %2F becomes a real separator and takes part in that resolution, so /a/b%2F..%2Fzz is matched as /a/zz. The query string is split off first and never participates in matching at all.

What Is an nginx location Block?

A location block tells nginx what to do with a request whose URI matches a pattern. A server block usually holds several, and the interesting part is not what each one does but which one nginx picks — because the selection rules are not the ones most people assume.

There are five forms. location = /path matches only when the whole URI is equal. location /path matches any URI starting with those characters. location ^~ /path is the same prefix comparison with one extra effect. location ~ regex and location ~* regex apply a PCRE pattern, case-sensitively and case-insensitively. location @name never takes part in URI matching at all and exists only as a target for try_files and error_page.

Selection runs in stages. First the URI is normalised: percent-decoded, . and .. resolved, repeated slashes collapsed, query string split off. Then an = location equal to the URI ends the search immediately. Then every matching prefix is compared and the longest is remembered — configuration order plays no part here at all. If that remembered prefix carries ^~, nginx stops and uses it. Otherwise the regex locations are tried in the order they appear in the file, and the first match wins, however specific a later one might be. If none matches, the remembered prefix is used.

Two of those rules pull in opposite directions, and that is where the confusion lives: prefixes are chosen by length regardless of order, regexes by order regardless of length. A config that reads correctly from top to bottom can still route a request somewhere you did not intend, and no amount of re-reading reveals it. This page replays the whole sequence against your own config and shows where each block dropped out.

# From the nginx documentation. Which block serves each request?
server {
    location = /                   { }   # A
    location /                     { }   # B
    location /documents/           { }   # C
    location ^~ /images/           { }   # D
    location ~* \.(gif|jpg|jpeg)$  { }   # E
}

#   /                        -> A   exact match, search ends here
#   /index.html              -> B   no regex matched, longest prefix used
#   /documents/document.html -> C   longer prefix than B
#   /images/1.gif            -> D   ^~ won the prefix stage, regex skipped
#   /documents/1.jpg         -> E   regex beats the longer prefix C

# The last two lines are the whole lesson: identical-looking prefixes
# behave differently because only one of them carries ^~.

Key Features

Every losing block, with the stage it was eliminated at

Each location you wrote gets a row: matched but shorter, skipped because a ^~ prefix won, unreachable because an earlier regex matched, or simply no match. Knowing why the others lost is usually what actually resolves the question.

The four-stage decision chain, replayed

Normalisation, exact match, longest-prefix memory, the ^~ short-circuit, regex in file order and the fallback are shown as discrete steps against your own configuration rather than described in the abstract.

The ^~ short-circuit made visible

When a ^~ prefix suppresses the regex phase, every skipped regex is labelled as skipped rather than quietly vanishing — and when the ^~ fails to apply because a longer plain prefix won, that is shown too.

Nested locations resolved, not flattened

nginx descends into the winning prefix and searches its children, so a nested regex runs before the parent level's. Nested blocks keep their indentation in the table so the structure stays readable.

PCRE-only syntax detected up front

Browsers run ECMAScript regexes, not PCRE. Atomic groups, possessive quantifiers, POSIX classes and escapes such as \A and \K are flagged instead of silently mis-evaluated, so a confident wrong answer is never presented as fact.

Nothing uploaded — it runs in your browser

Server configs carry upstream hostnames, internal ports and auth rules. Parsing is plain string work with no dependencies and no network calls, verified by an automated contract test on every build.

Other Ways to Answer This Question

nginx -T

Command line

Dumps the fully resolved configuration, which is invaluable for finding what is actually loaded. It will not tell you which location a given URI selects — that is the gap this page fills.

error_log ... debug

Live server

The most authoritative answer available: the "using configuration" line names the block nginx really chose. It needs root, a reload, and a server you can reach — so it answers after deployment, not before.

Config syntax checkers

Hosted service

Strong at whole-file linting and security rules. They generally run server-side, which means uploading a configuration that contains internal hostnames and certificate paths.

Reading the docs

Reference

The nginx documentation states the algorithm precisely and is worth reading once. Applying it by hand to eight blocks and a URI is where the errors creep in, because two of the rules point in opposite directions.

nginx location Matching Examples

A regex beats a longer prefix

location /documents/  ·  location ~* \.(gif|jpg|jpeg)$  ·  GET /documents/1.jpg
~* \.(gif|jpg|jpeg)$ wins

The prefix /documents/ matches and is the longest prefix in the file, so nginx remembers it — then evaluates the regexes anyway and hands the request to the first one that matches. Length loses to the regex phase. Had that prefix been written ^~ /documents/, the regex would never have run at all. This is the example from the nginx documentation, and it is the single most useful one to internalise.

The upload directory that executes PHP

location /uploads/  ·  location ~ \.php$ { fastcgi_pass ... }  ·  GET /uploads/evil.php
~ \.php$ wins — the upload lands in the interpreter

The prefix /uploads/ matches, but a plain prefix does not stop the regex phase, so a file someone uploaded is handed straight to PHP-FPM. Writing location ^~ /uploads/ short-circuits the regex phase and closes it. This is not a hypothetical: it is the shape behind a long line of upload-to-RCE reports, and the difference between vulnerable and safe is two characters.

^~ that quietly does nothing

location ^~ /a/  ·  location /a/b/  ·  location ~ \.php$  ·  GET /a/b/x.php
~ \.php$ wins — the ^~ never applied

^~ only suppresses the regex phase when it is itself the longest matching prefix. Here /a/b/ is longer, so it is the one nginx remembers, and its plain modifier lets the regex phase run. The ^~ block is still in the file, still looks protective, and has no effect on this request. Reading a config top to bottom will not reveal that; comparing prefix lengths will.

/static also captures /staticfoo

location /static  ·  location /static/  ·  GET /staticfoo
/static wins

Prefix matching compares characters, not path segments. /staticfoo starts with /static, so it matches, and /static/ does not match at all because the URI has no slash in that position. Anything reachable under a path that merely starts with the same letters is served by that block — which is how a /static rule ends up serving /static-backup.

The first regex wins, not the best one

location ~ ^/a  ·  location ~ ^/a/b/c$  ·  GET /a/b/c
~ ^/a wins; ~ ^/a/b/c$ is unreachable

Regexes are evaluated in the order they appear in the file and the first match ends the search. The second block is more specific and matches this URI exactly, and it will never run — for any request. Prefix locations are chosen by length regardless of order; regex locations are chosen by order regardless of specificity. Mixing those two rules up is the usual reason a rule "stops working" after someone tidies the file.

Encoded traversal resolves before matching

location /a/  ·  location /b/  ·  GET /a/b%2F..%2Fzz
$uri becomes /a/zz, so /a/ wins

nginx percent-decodes, resolves . and .. and collapses repeated slashes before any location is consulted. %2F decodes to a real separator and then participates in that resolution, so this target does not stay inside /a/b/ — it lands on /a/zz. Matching against the target you typed rather than the normalised $uri gives the wrong answer here.

How to Use the nginx location Tester

  1. 1

    Paste the server block

    Drop in a whole server block, or just the location blocks you are reasoning about. Nested locations are understood, and the original line numbers are kept so the table matches your file.

  2. 2

    Enter the request URI

    Type the path as it arrives on the wire, including any percent-encoding and query string. The three rows above the table show how it is normalised before matching.

  3. 3

    Read the winner, then the losers

    The verdict card names the selected block and the one-line reason. The decision table underneath explains every other block: which stage it was eliminated at and why.

  4. 4

    Check the diagnostics

    Unanchored regexes, prefixes missing a trailing slash, a ^~ carrying a regex pattern, unreachable duplicates and upload directories reachable by a PHP regex are all reported.

  5. 5

    Share the exact state

    Copy link encodes the config and URI in the URL fragment so a colleague opens precisely what you are looking at. Fragments are never transmitted to a server.

Common nginx location Mistakes

Expecting ^~ to outrank a longer prefix

^~ is only consulted on the prefix that already won on length. A longer plain prefix wins first, and the regex phase then runs as though the ^~ were not there.

✗ Wrong
location ^~ /a/ { }
location /a/b/ { }
location ~ \.php$ { }
# /a/b/x.php -> ~ \.php$
✓ Correct
location ^~ /a/ { }
location ^~ /a/b/ { }
location ~ \.php$ { }
# /a/b/x.php -> ^~ /a/b/

A prefix without a trailing slash catching siblings

Prefix matching compares characters rather than path segments, so the block also serves every sibling path that starts with the same letters.

✗ Wrong
location /static { root /var/www; }
# also serves /staticfoo and /static-backup
✓ Correct
location /static/ { root /var/www; }
location = /static { return 301 /static/; }

Putting the specific regex below the broad one

Regexes are tried in file order and the first match ends the search, so the more precise block never executes for any request.

✗ Wrong
location ~ ^/api { }
location ~ ^/api/v2/users$ { }
# the second is unreachable
✓ Correct
location ~ ^/api/v2/users$ { }
location ~ ^/api { }

Writing a regex after ^~

^~ takes a literal prefix. nginx loads the file without complaint and the block simply never matches anything, which makes it hard to spot in review.

✗ Wrong
location ^~ "\.php$" { deny all; }
✓ Correct
location ~ \.php$ { deny all; }

Assuming the query string takes part in matching

The query string is split off during normalisation, so a location can never match on it. Read $arg_name inside the block instead.

✗ Wrong
location /search?q= { }
# never matches anything
✓ Correct
location /search {
    if ($arg_q = "") { return 400; }
}

What You Can Do with the nginx location Tester

Check a config before it reaches production
Reason about the routing of a server block you have edited but not deployed. The failure this catches is the one that only appears under a particular URI shape, which is exactly the kind a smoke test after reload tends to miss.
Find out why a rule stopped working
A regex that used to run and now does not is almost always a victim of ordering: something matched earlier, or a ^~ appeared above it. The table marks the block unreachable and names the block that took the request.
Audit an upload or media directory
Load the preset that reproduces the upload-execution shape, then paste your own paths. If a PHP regex is taking requests inside a directory that accepts writes, the diagnostic says so and names both blocks.
Settle a review comment with evidence
Copy link captures the exact config and URI in the URL fragment. Dropping that into a pull request replaces an argument about precedence with a decision table anyone can re-run.
Teach the selection algorithm
The two reference tables are static, crawlable material you can point at, and the preset chips demonstrate each trap without needing a server to break. The ^~ example in particular tends to end the debate quickly.

How nginx location Selection Works

Normalisation happens before any location is consulted
The URI is percent-decoded, . and .. segments are resolved and repeated slashes are collapsed, and only then is a location chosen. A decoded %2F becomes a real separator and participates in that resolution, so /a/b%2F..%2Fzz is matched as /a/zz. Three decoded characters are exceptions and stay literal: %25, %23 and %3F — which is why /a%3Fx=1 has a question mark in its path and an empty query string. A + is not a space here; only %20 is. Traversal above the root and an invalid escape are both rejected with 400 before matching starts.
Prefix length decides, configuration order does not
Every prefix location that matches is compared and the longest wins, whether it appears first or last in the file. Comparison is by characters, not by path segments, so /static matches /staticfoo. The winner is remembered rather than used immediately, because the regex phase may still override it.
^~ suppresses regexes; it does not raise priority
The modifier is checked only on whichever prefix already won on length. If a longer plain prefix also matches, that one is remembered instead and the regex phase runs as usual — the ^~ block has no effect on the request at all. Placing ^~ on a nested location does not protect against a regex declared at the outer level, and it never suppresses regexes nested inside its own block.
Regexes run in file order and the first match ends the search
nginx keeps regex locations in the order they were written and does not sort them. Specificity, length and anchoring have no bearing on which is tried first, so a precise pattern placed below a broad one is dead configuration. A matched regex location is still descended into, so its nested locations are searched afterwards.
PCRE and ECMAScript are not the same language
nginx compiles location regexes with PCRE, without UTF or multiline mode, so patterns operate on bytes and ^ anchors to the start of the URI only. One difference has security weight: PCRE's $ also matches just before a trailing newline, so a URI ending in %0A still satisfies \.php$ even though a JavaScript engine would refuse it. That behaviour is emulated here and reported, because it is a known way to slip past rules keyed on a file extension.

nginx location Best Practices

Protect writable directories with ^~, not a plain prefix
Any directory that accepts uploads needs location ^~ /uploads/ so the regex phase cannot hand a stored file to an interpreter. A plain prefix looks equivalent and is not.
Give directory prefixes a trailing slash
Write location /static/ rather than location /static unless you deliberately want /staticfoo and /static-backup served by the same block. Add a separate location = /static when the bare path also needs handling.
Order regexes from most specific to least
The first match wins, so a broad pattern above a precise one makes the precise one unreachable. Keeping regexes few and ordered deliberately is easier to maintain than reasoning about overlap after the fact.
Anchor regexes that are meant to match a path prefix
location ~ /admin searches anywhere in the URI and matches /public/admin/x. Write ~ ^/admin when you mean the start. Anchoring only at the end is normal and correct for extension routing.
Re-check routing after any reordering
Moving blocks is safe for prefixes and changes behaviour for regexes. Since a diff that only reorders lines looks harmless in review, re-running the affected URIs is the cheapest way to catch it.

nginx location Tester FAQ

How do I debug which location nginx selected?
Paste the config and the request URI into this tester to see the winning block and why each other block was eliminated. On a running server, add error_log /var/log/nginx/debug.log debug; and look for the "using configuration" line, which names the selected location. The two approaches answer different questions: the log tells you what a live server did, and this page tells you what a config you have not deployed yet would do.
Why is my nginx location regex not working?
Usually one of three reasons, and the decision table names which. An earlier regex already matched, so yours never ran — regexes are tried in file order and the first match wins. Or the longest matching prefix carries ^~, which skips the regex phase completely. Or the regex is fine but the URI is not what you think: matching runs against the normalised path, after percent-decoding and .. resolution, with the query string removed.
Why is my exact match not triggering?
An = location requires the whole URI to be equal, not to start with the pattern. location = /a/ does not match /a, and location = /a does not match /a/b. Trailing slashes are ordinary characters here, so the two forms are different strings. When an exact location does match, the search stops immediately and nothing else is even compared.
Does nginx match the query string in a location block?
No. The query string is split off during normalisation and location selection runs against the path alone. That is why location = /a matches a request for /a?x=/b. If you need to branch on a parameter you have to read $arg_name or $args inside the block. One subtlety worth knowing: %3F decodes to a literal question mark that stays in the path, so /a%3Fx=1 has an empty query string and a path containing ?.
Can I use JavaScript regex syntax in an nginx location?
No — nginx uses PCRE, and the differences matter. This page runs in your browser, where only ECMAScript regular expressions exist, so PCRE-only constructs are detected and flagged rather than silently mis-evaluated: atomic groups, possessive quantifiers, inline modifiers such as (?i), POSIX classes such as [[:alpha:]], and escapes like \A and \K that JavaScript quietly reads as ordinary letters. When a location is flagged, the remaining candidates are still evaluated in nginx's order, but that block's verdict needs checking on a real server. If you are writing the pattern itself, the regex tester covers ECMAScript syntax in depth.
Are nginx location prefix matches case-sensitive?
On Linux, yes — location /Static/ does not match /static/x. On case-insensitive filesystems such as macOS and Cygwin, nginx compares prefixes case-insensitively and additionally forces every regex location to behave like ~*. This page models the Linux behaviour, which is what production servers almost always run. If you develop on a Mac and deploy to Linux, that difference can hide a broken rule until it ships.
Does try_files change which location block was selected?
No. Location selection finishes first; try_files runs afterwards, inside whichever block already won. If a request never reaches the block containing your try_files, the directive is irrelevant — the usual cause is a ~ \.php$ regex taking the request before the prefix block with the fallback ever gets to act. An internal redirect issued later does restart matching, so a rewritten URI is resolved against the location list again from the top.
Why does nginx return 301 when I request a directory without a slash?
Two different mechanisms produce that. If a location whose name ends in / carries proxy_pass or another *_pass directive, a request for the same path without the slash is answered with a 301 during location selection — before any regex is evaluated. Separately, the static file module issues a 301 when the path resolves to a real directory on disk. The first is visible here; the second depends on your filesystem. Adding location = /path suppresses the first.
Do nested location blocks change the outcome?
Yes, and in a way that is easy to miss. nginx descends into the winning prefix location and searches its children, so a nested regex is tried before the parent level's regexes. A ^~ on the outer block does not shield it from its own nested regexes, and nesting can make a globally longer prefix unreachable when a sibling at the outer level wins first. Nested blocks are modelled here with the same indentation you wrote them in.
Is my nginx configuration uploaded anywhere?
No. Parsing and matching happen locally in your browser with plain string operations — there is no server call and nothing is retained. This matters more here than for most tools, because a real server block contains upstream hostnames, internal ports, certificate paths and auth rules. You do not have to take our word for it: open your browser's developer tools and watch the Network panel stay silent while you type, or disconnect entirely and keep testing. The absence of any external request is also enforced by an automated contract test on every build, so it cannot quietly regress.

Related Tools

View all tools →