Location Blocks & Request Routing In Depth

Nginx In Depth

Chapter 3 · Location Blocks & Request Routing In Depth

Web Servers Fundamentals Chapter 4 introduced a plain location /path/ {} block as a way to match a request path. In practice, Nginx supports several different kinds of location match, each with its own precedence — and getting that precedence wrong is one of the most common real-world sources of "why isn't this location matching what I expect" confusion.

Location Matching Types

location = /exact { /* exact match */ } location ^~ /assets/ { /* prefix, stop checking regex if matched */ } location ~ \.php$ { /* regex, case-sensitive */ } location ~* \.(jpg|png)$ { /* regex, case-insensitive */ } location /blog/ { /* plain prefix match */ }

Nginx supports five distinct forms: an exact match (=), a prefix match that stops further checking (^~), a case-sensitive regex (~), a case-insensitive regex (~*), and a plain prefix match with no modifier at all. Each of these is a different kind of test against the request path, not just a different way of writing the same thing.

Matching Precedence: The Actual Order Nginx Checks

Nginx evaluates these in a fixed order, regardless of what order they're written in the config file: first, any exact match (=) — if one matches, Nginx stops immediately. Next, the longest matching prefix among plain and ^~ prefixes is found; if that longest match used the ^~ modifier, Nginx stops there too, skipping regex entirely. Otherwise, every regex location (~/~*) is checked in the order they appear in the config file, and the first one that matches wins. Only if no regex matches at all does Nginx fall back to that earlier plain-prefix match. Regex order genuinely matters — prefix order does not.

try_files

location / { try_files $uri $uri/ /index.html; }

try_files checks a list of candidates in order and serves the first one that actually exists — conceptually the same idea as Web Servers Fundamentals Chapter 5's directory-index list, but applied to serving a request generally, not just a bare-directory request specifically. The line above is the standard single-page-application pattern: try the exact URI as a real file, then as a directory, and if neither exists, fall back to serving index.html and letting the client-side app's own router take over from there.

rewrite

rewrite ^/old-path/(.*)$ /new-path/$1 permanent;

rewrite uses a regular expression to rewrite the request URI itself. Adding permanent sends the client an actual HTTP 301 redirect to the new URL; redirect sends a 302; omitting both flags rewrites the URI internally — Nginx serves the new path directly without the client ever seeing a redirect at all, which is a very different, easy-to-miss outcome from what the directive's name might suggest.

SyntaxTypePrecedence
location = /pathExact matchChecked first, wins immediately
location ^~ /path/Prefix, stops regex checkingChecked second, wins if longest prefix
location ~ patternRegex, case-sensitiveChecked in file order, first match wins
location ~* patternRegex, case-insensitiveSame tier as ~, file order
location /path/Plain prefixFallback if no regex matches
Where try_files shows up in a real deployment
Every single-page application built in React, Vue, or Svelte (this site's own frontend framework courses) needs exactly the try_files $uri $uri/ /index.html; pattern shown above once deployed behind a real Nginx server — without it, refreshing the browser on any client-side route other than the homepage returns a 404, since that path genuinely doesn't exist as a file on disk and only the app's own JavaScript router knows how to handle it.
Location blocks are not checked top-to-bottom like a firewall rule list
It's a very common and reasonable-sounding assumption that Nginx checks location blocks strictly in the order they're written, the way a firewall's rule list works. That's not what happens: per this chapter's own precedence section, exact matches and the longest prefix are found algorithmically regardless of file order, and only among regex blocks specifically does file order actually decide a tie. Writing location blocks assuming top-to-bottom evaluation is one of the most common real sources of "my more specific rule isn't matching" bugs.

Hands-On Exercises

Exercise 1

Write the Nginx location block(s) needed to serve a single-page application: any request that matches a real file or directory should be served directly, and everything else should fall back to /index.html.

📄 View solution
Exercise 2

A config has both `location /images/ { ... }` and `location ~* \.(jpg|png)$ { ... }`, with the plain prefix block written first in the file. A request comes in for /images/photo.jpg. Explain which block actually handles it, and why the order they're written in doesn't decide the outcome here.

📄 View solution
Exercise 3

Explain the practical difference between `rewrite ^/old/(.*)$ /new/$1 permanent;` and the same rewrite with no flag at all, from the perspective of what the client's browser actually sees.

📄 View solution

Chapter 3 Quick Reference

  • Five location match types: exact (=), priority prefix (^~), regex (~/~*), and plain prefix
  • Precedence order: exact match → longest prefix (stopping if ^~) → regex blocks in file order → fallback to the longest plain prefix
  • Regex order matters; prefix order does not — a common source of routing bugs
  • try_files checks candidates in order, serving the first that exists — the standard SPA fallback pattern is try_files $uri $uri/ /index.html;
  • rewrite with permanent/redirect sends a real HTTP redirect; with no flag, it rewrites the URI internally with no redirect visible to the client