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.