bitlvl
EN

nginx location tester

nginx picks a location by rules that are easy to misremember, and the usual way to find out is to deploy and curl. Paste your config and a URL instead: the trace shows every location considered, why it won or lost, and what happens next.

Nothing is sent anywhere; the matching runs in your browser.

How nginx chooses a location

  1. The URI is normalised first: percent-decoding, repeated slashes merged, . and .. resolved. The query string is never part of location matching.
  2. All prefix locations (no modifier, =, ^~) are compared. An = exact match wins immediately. Otherwise the longest matching prefix is remembered.
  3. If that longest prefix has ^~, it wins and regexes are skipped.
  4. Regex locations (~ case-sensitive, ~* case-insensitive) are tested in the order they appear in the file. The first match wins, even if a longer prefix matched earlier.
  5. If no regex matches, the remembered longest prefix is used.
  6. Inside the winning location the same procedure runs again for nested locations.

Two consequences surprise people most: a plain location /images/ loses to location ~ \.png$ for /images/a.png, because regexes are checked after prefixes; and location /api also matches /apiary, because prefixes are string prefixes, not path segments. Use /api/ or ^~ when you mean it.

proxy_pass and the trailing slash

If proxy_pass has a URI part (anything after the host, even just /), nginx replaces the part of the request URI that matched the location prefix with it. location /api/ { proxy_pass http://app/; } turns /api/users into /users. Without a URI part, the request URI goes through unchanged. Mismatched slashes (/api vs /api/, or http://app vs http://app/) produce //users or users — the tester prints the exact upstream URI so you can see it. A URI part is not allowed in a regex location unless a rewrite changed the URI first.

root vs alias

root appends the whole URI to the directory: root /srv; location /static/ serves /srv/static/x.css. alias replaces the location prefix: alias /srv/assets/; location /static/ serves /srv/assets/x.css. With alias the trailing slashes of the location and the alias must agree, and in a regex location alias must use captures.

rewrite flags

FlagEffect
laststop processing rewrites, start location matching again with the new URI (max 10 times, then 500)
breakstop rewrites, stay in the current location with the new URI
redirectsend a 302 to the client
permanentsend a 301 to the client
(none)continue with the next rewrite; at server level the result is then matched against locations

try_files

Each argument is checked as a file (or directory, with a trailing slash) relative to root/alias; the first that exists is served. The last argument is the fallback and is not checked: it is an internal redirect to a URI (which goes through location matching again), a named location @name, or =404-style status code. The tester cannot see your filesystem, so it lists the paths that would be checked.

add_header inheritance

add_header is inherited from the server level only when the location has no add_header of its own. One add_header Cache-Control in a location silently drops the security headers set at server level. The trace flags this.

Questions

Does it support the full nginx config syntax?

The parser handles the real syntax: nested blocks, comments, quoted strings, regex arguments, include directives are listed but not followed (paste the included file too). Variables other than $uri, $request_uri, $args and regex captures are left as text, and “if” blocks are shown but not evaluated.

Why does my regex behave differently in nginx?

nginx uses PCRE; this page uses JavaScript regular expressions. They agree on almost everything used in location blocks, but possessive quantifiers, recursion and some lookbehind forms differ. The trace says when a regex could not be compiled.

Can it test which server block answers for a Host?

Yes. Paste several server blocks and fill in the Host field. Selection follows nginx: exact name, longest wildcard starting with *, longest wildcard ending with *, first matching regex, then default_server, then the first server for that port.

How do I test on the real server?

nginx -T prints the full merged config (all includes resolved) which you can paste here. For a live check, curl -sI -H "Host: example.com" http://127.0.0.1/path and read the response; add_header X-Debug-Location "$uri" temporarily if you need to see which block answered.