Nginx Location Blocks: Match Rules and Priority

Sooner or later, every Nginx configuration grows beyond a single catch-all rule. You want PHP files to reach PHP-FPM, static assets to be served directly with long cache headers, one endpoint to be proxied to an application server, and an admin path to be protected with basic auth. Each of these rules lives in its own location block.
The part that trips people up is not writing a location block, it is predicting which one Nginx will pick when a request could match more than one. Nginx does not scan top to bottom. It uses a specific priority order that mixes prefix length, match type, and regex ordering. This guide walks through the match types, the exact order Nginx follows, and the patterns you will use most often.
Location Block Syntax
A location block lives inside a server block
and defines how Nginx handles requests whose URI matches a given pattern:
location [modifier] pattern {
# directives
}The modifier is optional. When it is omitted, Nginx treats the pattern as a prefix. The pattern is a string (for prefix and exact matches) or a regular expression (when the modifier is ~ or ~*).
A minimal server block that uses two location blocks looks like this:
server {
listen 80;
server_name example.com;
root /var/www/example.com;
location / {
try_files $uri $uri/ =404;
}
location /api/ {
proxy_pass http://127.0.0.1:3000;
}
}Requests for /about.html fall into the first block and are served as static files. Requests for /api/users fall into the second block and are proxied to the application.
The Five Match Types
Nginx supports five kinds of location matches. Each one uses a different modifier, and each one has its own role in the priority order covered in the next section.
location /path/- Prefix match. Matches any URI that begins with the given string. This is the default when no modifier is used.location = /path- Exact match. Matches only when the request URI is exactly equal to the given string.location ^~ /path/- Preferential prefix match. Same matching rules as a plain prefix match, but skips regex and predicate locations at the same level when this block is selected as the longest prefix. Nested locations can still be selected.location ~ pattern- Case-sensitive regex match. Matches if the URI matches the regular expression.location ~* pattern- Case-insensitive regex match. Same as~, but ignores letter case.
There is also a sixth form, location @name, for named locations. Named locations are not used during the normal match process; they are jumped to from directives such as try_files and error_page. We cover them later in this guide.
Nginx 1.31.5 (mainline) adds one more form, the predicate location. It starts with a variable, for example location $arg_preview, and matches when the variable is not empty and not 0. Predicate locations are checked after the regex locations, in the order they appear in the configuration. A longest prefix match with ^~ skips them at the same level, just as it skips regex. The Nginx 1.28 package in Ubuntu 26.04 does not support them.
How Nginx Picks a Location
When a request comes in, Nginx does not walk through location blocks top to bottom. It picks the block that wins according to a fixed priority order.
For locations defined directly inside a server block, the order is:
- Look for an exact match (
=). If one matches, stop and use it. - Look at all prefix matches (plain and
^~) and remember the longest one that matches. - If the longest prefix match was defined with
^~, skip the server-level regex and predicate checks. Use that prefix or a matching location nested inside it. - Otherwise, go through regex matches (
~and~*) in the order they appear in the configuration. The first one that matches wins. - On Nginx 1.31.5 and later, if no regex matches, check predicate locations in configuration order. The first true predicate wins.
- If no regex or predicate matches, fall back to the longest prefix match remembered in step 2.
The consequences of this order are worth pausing on. Regex blocks are checked in source order, but prefix blocks are not; the longest match wins regardless of position in the file. The ^~ modifier skips regex and predicate checks at the level where that prefix is selected. Nested locations add another search inside the selected prefix, as shown in Nested Locations
.
A Worked Example
The easiest way to understand the priority order is to trace a few requests through a real configuration:
server {
listen 80;
server_name example.com;
root /var/www/example.com;
location = / {
return 200 "home\n";
}
location / {
try_files $uri $uri/ =404;
}
location /images/ {
expires 30d;
}
location ^~ /downloads/ {
autoindex on;
}
location ~* \.(png|jpg|jpeg|gif)$ {
expires 7d;
access_log off;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
}Now trace what happens for a few requests:
GET /: The=block matches exactly and wins immediately. Nginx returns the texthome.GET /about.html: No exact match, no^~match, and no regex match. The longest prefix match is/, so thetry_filesblock serves the static file.GET /images/logo.png: The longest prefix match is/images/, which is a plain prefix (no^~), so regex checking still happens. The first regex to match is the image extension block (~*), so that block wins and the file is served with a 7-day expiry.GET /downloads/setup.exe: The longest prefix match is/downloads/, defined with^~. Because of the^~, regex checking is skipped and the directory listing block wins.GET /info.php: The longest prefix match is/. Regex checking runs, and the\.php$block matches, so the request is passed to PHP-FPM.
Notice that the block order in the file did not change the outcome for prefix matches. It only changed the outcome for regex matches, where the first match wins.
Named Locations
A named location starts with @ and does not take part in the matching logic. You can only enter a named location by being redirected there from another directive:
server {
listen 80;
server_name example.com;
root /var/www/example.com;
location / {
try_files $uri $uri/ @fallback;
}
location @fallback {
proxy_pass http://127.0.0.1:3000;
}
}In this configuration, Nginx first tries to serve the request as a static file. If the file is not found, try_files jumps to @fallback, which proxies the request to an application server on port 3000. This is a common pattern for single-page apps and for frameworks that handle their own routing.
Named locations are also useful with the error_page directive, where you can route errors into a named block that returns a custom response.
Nested Locations
A location block can contain other location blocks. This lets you set a rule for a path and then pick a more specific handler for some of the files under it:
server {
listen 80;
server_name example.com;
root /var/www/example.com;
location ~* \.(png|jpg|jpeg|gif)$ {
expires 7d;
}
location /media/ {
location ~* \.jpg$ {
expires 30d;
}
}
}When a request for /media/photo.jpg comes in, Nginx selects /media/ as the longest prefix match and then searches the locations nested inside it. The nested regex matches, so the image is served with a 30-day expiry. The top-level regex is never used, even though it appears first in the file.
If no nested location matches, Nginx goes back to the top-level regex blocks. A request for /media/clip.png does not match the nested \.jpg$ regex, so it falls through to the top-level image block and gets the 7-day expiry. A request for /media/notes.txt matches no regex at all and is handled by /media/ itself.
The ^~ modifier does not skip regex locations nested inside that prefix. A nested ^~ also does not automatically suppress regex checks at outer levels if the search returns there. The matching prefix at each level controls whether that level’s regex and predicate locations are checked.
Exact match locations (=) cannot contain nested locations. Named locations cannot contain them either, and they cannot be nested inside another block.
Common Patterns
Most Nginx configurations end up using a handful of recurring location patterns. The snippets below show the shapes you will reach for most often.
Serve Static Files with a Fallback
location / {
try_files $uri $uri/ /index.html;
}Nginx tries the requested URI as a file, then as a directory, and falls back to /index.html. This works well for single-page apps where the router lives in the browser.
Cache Long-Lived Assets
location ~* \.(?:css|js|woff2?|png|jpg|jpeg|gif|svg|ico)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}Assets whose content is fingerprinted by the build pipeline can be cached aggressively. The access_log off directive keeps the access log focused on real page requests; see the Nginx log files guide
for where those logs live and how to tune them.
This block has its own add_header directive. Any add_header lines in the server block no longer apply to these files; see Common Mistakes
for how to keep them.
Pass PHP Requests to PHP-FPM
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
}The regex matches any URI that ends with .php. The fastcgi-php.conf snippet ships with the Nginx package on Debian and Ubuntu. It checks that the PHP file exists, then includes fastcgi.conf to set SCRIPT_FILENAME and the related parameters.
On distributions without this snippet, use include fastcgi.conf; instead. The fastcgi_params file does not set SCRIPT_FILENAME. If you include only that file, PHP-FPM cannot find the script and every PHP request returns File not found.
Proxy a Path to an Application
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}For a deeper walkthrough of the proxy headers and common pitfalls, see the Nginx reverse proxy guide .
Protect an Admin Path
location ^~ /admin/ {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
}When /admin/ is the longest matching prefix at the server level, ^~ skips regex blocks at that level, such as a generic static-asset rule. Locations nested inside /admin/ are still checked. Basic auth sends credentials in clear text, so serve the site over HTTPS; see how to redirect HTTP to HTTPS in Nginx
.
The ^~ modifier also skips the PHP block at the server level. Without the nested location ~ \.php$, Nginx sends PHP files under /admin/ as static files, and anyone who logs in can download their source code. The nested block inherits the auth_basic settings from /admin/, so PHP requests still require a password.
Testing the Configuration
After changing location blocks, always test the Nginx configuration before reloading the service:
sudo nginx -tIf the syntax check passes, reload Nginx so the change takes effect without dropping active connections:
sudo systemctl reload nginxIf the test fails, Nginx prints the file name and line number that caused the error. Fix that issue first, then run sudo nginx -t again.
Checking Which Location Handles a Request
When a request does not behave the way you expect, the quickest check is to tag each location block with a temporary response header. Add an add_header line with a unique value to every block you want to trace:
location /images/ {
add_header X-Location "images" always;
expires 30d;
}
location ~* \.(png|jpg|jpeg|gif)$ {
add_header X-Location "image-regex" always;
expires 7d;
}Reload Nginx, then use curl
to request the URL. The -I option sends a HEAD request and prints only the response headers:
curl -I http://example.com/images/logo.pngHTTP/1.1 200 OK
Server: nginx/1.28.3 (Ubuntu)
Date: Sun, 18 Oct 2026 15:44:28 GMT
Content-Type: image/png
Content-Length: 16
Last-Modified: Sun, 18 Oct 2026 15:44:12 GMT
Connection: keep-alive
ETag: "6ad4e94c-10"
Expires: Sun, 25 Oct 2026 15:44:28 GMT
Cache-Control: max-age=604800
X-Location: image-regex
Accept-Ranges: bytesThe X-Location header shows that the image regex handled the request, not the /images/ prefix block. The max-age=604800 value in Cache-Control matches the 7-day expiry of that block. With the always parameter, the tag also appears on error responses such as 404 or 403.
Remove the test headers when you are done. An add_header in a location replaces the headers inherited from the server block.
Quick Reference
For a printable quick reference, see the Nginx cheatsheet .
| Modifier | Meaning | When it wins |
|---|---|---|
= | Exact match | Checked first; a match ends the search |
^~ | Preferential prefix match | Longest prefix at its level; skips sibling regex and predicates |
~ | Case-sensitive regex | First match in source order |
~* | Case-insensitive regex | First match in source order |
$variable | Predicate (Nginx 1.31.5+) | First match after regex, before the prefix fallback |
| (none) | Prefix match | When it is the longest prefix and no regex or predicate wins |
@name | Named location | Only reachable from directives |
Common Mistakes
A few patterns cause most of the confusion around location blocks.
Assuming top-to-bottom evaluation. Moving a prefix block higher in the file does not make it more likely to match. Prefix matches are chosen by length, not by position.
Forgetting the scope of ^~. When the longest matching prefix at a given level uses ^~, Nginx skips regex and predicate blocks at that level. Regex locations nested inside the prefix can still run, and a nested ^~ does not automatically suppress regex checks at outer levels. A longer matching prefix also takes precedence over a shorter ^~ prefix.
Trailing slashes. location /images/ and location /images behave differently. The first only matches URIs that start with /images/, while the second also matches /imagesfoo. Stick to trailing slashes for directory-like prefixes. When a location with a trailing slash uses proxy_pass (or fastcgi_pass and similar directives), a request for the same path without the slash gets a 301 redirect. For example, location /api/ answers a request for /api with a redirect to /api/. If the application should handle /api itself, add an exact location = /api block.
Using proxy_pass with a trailing slash on the upstream. Writing proxy_pass http://backend/; rewrites the path in a different way than proxy_pass http://backend;. The proxy_pass URI table
in our reverse proxy guide shows what the upstream receives for each combination, and the Nginx proxy_pass documentation
explains the URI replacement rules in detail.
Setting add_header in a location. Nginx inherits add_header directives from the server block only when a location does not define any of its own. A single add_header in a location, such as the Cache-Control header in the asset caching example, drops every header set at the server level, including security headers like X-Frame-Options. Repeat the headers you need inside the location, or put them in a shared file and include it in each block. On Nginx 1.29.3 and later, add_header_inherit merge; keeps the inherited headers and adds the new ones. The Nginx 1.28 package in Ubuntu 26.04 does not have this directive.
Regex order. When two regex blocks can match the same URI, the one that appears first in the config wins. If requests are landing in the wrong regex block, check the order.
Conclusion
Most Nginx misconfigurations come from assumptions about the order of evaluation, not from the directives themselves. Once you know that exact matches win first, that ^~ can short-circuit the regex pass, and that regex blocks are tried in source order, the behavior of a complex server block becomes predictable.
Tags
Linuxize Weekly Newsletter
A quick weekly roundup of new tutorials, news, and tips.
About the authors

Dejan Panovski
Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 1000+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.
View author page