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 tells Nginx to stop searching for regex matches once this block is selected as the longest prefix. -
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.
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.
The order Nginx follows 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
^~, stop and use it. - Otherwise, go through regex matches (
~and~*) in the order they appear in the configuration. The first one that matches wins. - If no regex 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 changes the outcome by skipping the regex pass entirely when it is the longest prefix match, even if a regex block would also have matched.
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$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
include fastcgi_params;
}
}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.
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.
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 shipped with most distributions sets the correct SCRIPT_FILENAME and related parameters.
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;
}The ^~ modifier makes sure this block wins over any regex block (for example, a generic static-asset rule), so the auth challenge is not accidentally bypassed by a more specific pattern. Basic auth sends credentials in clear text, so serve the site over HTTPS; see how to redirect HTTP to HTTPS in Nginx
.
Test 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.
Quick Reference
| Modifier | Meaning | Priority |
|---|---|---|
= |
Exact match | 1 (highest) |
^~ |
Preferential prefix match | 2 (skips regex) |
~ |
Case-sensitive regex | 3 (in source order) |
~* |
Case-insensitive regex | 3 (in source order) |
| (none) | Prefix match | 4 (longest 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 that ^~ skips regex. If a request matches both a ^~ prefix and a regex block, the regex block is not evaluated. This is a feature, not a bug, but it is easy to forget when debugging why a regex rule is being ignored.
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.
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 Nginx proxy_pass documentation
explains the URI replacement rules in detail.
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.
![]()