Nginx Location Block Priority Explained

Last updated Sunday, May 17, 2026 1:04 pm Joshua James 9 min read

Nginx location block priority explains why a request such as /assets/app.css may be handled by a regex block even when a normal location /assets/ block exists. The matching rules are not simple top-to-bottom order. Nginx first chooses the best literal prefix, then may still let the first matching regex override that prefix unless the selected prefix uses ^~.

The risk shows up most often after a small routing change: static assets start hitting an extension regex, an API path falls into the wrong proxy, or a fallback location catches requests that should have stayed in a narrower block. A local test server makes those outcomes visible before the same rule reaches a production virtual host.

Test location changes before applying them to a production virtual host. A broad regex, misplaced ^~ prefix, or wrong fallback location can change routing for every request under that server block.

Understand Nginx Location Block Priority

The Nginx location directive selects configuration by matching the request URI. Nginx compares the normalized URI path, not the query string. A request for /app.js?v=2 is matched as /app.js, so query arguments do not make one location block win over another.

Nginx location priority follows this sequence:

  1. Check exact locations such as location = /path. If one matches, Nginx stops searching.
  2. Find the longest matching prefix location, including normal prefixes and ^~ prefixes.
  3. If the longest matching prefix uses ^~, Nginx uses that location and skips regex checks.
  4. If the longest matching prefix does not use ^~, Nginx checks regex locations in the order they appear.
  5. If the first matching regex exists, Nginx uses that regex location.
  6. If no regex matches, Nginx uses the longest prefix location found earlier.

That middle step is the part many configurations get wrong. A normal prefix block is remembered, but it does not automatically win. A regex block can still override it. A ^~ prefix wins only when it is the longest matching prefix for that URI.

Compare Nginx Location Block Modifiers

The modifier before the URI controls how the location is matched. Match the modifier to the path you need to protect, then decide whether regex blocks should still be allowed to run.

Location TypeExampleHow Nginx Treats ItBest Use
Exact matchlocation = /healthMatches only the exact URI and stops all further location searching.Health checks, root path, favicon, one specific endpoint.
Priority prefixlocation ^~ /assets/Wins over regex only when it is the longest matching prefix.Static directories, uploads, protected paths that should not fall into a regex handler.
Normal prefixlocation /api/Can be overridden by the first matching regex unless no regex matches.General path routing, default site handling, application fallback paths.
Case-sensitive regexlocation ~ \.php$Checked in file order after prefix selection unless the chosen prefix uses ^~.PHP handlers, extension-specific routes, strict pattern matches.
Case-insensitive regexlocation ~* \.(jpg|png)$Same priority as ~; the first matching regex in the config wins.Static extension rules where uppercase and lowercase extensions should both match.
Named locationlocation @appNot used for normal browser requests. It is reached by internal redirects such as try_files or error_page.Application fallback, upstream fallback, internal error handling.

Regex order matters because Nginx does not pick the most specific regex. It picks the first regex that matches after the prefix phase allows regex evaluation. Keep specific regex blocks above broad regex blocks, or use a ^~ prefix when an entire path should bypass regex handling.

Test Nginx Location Block Priority Safely

The easiest way to understand location priority is to run a local-only test server. This fixture listens on 127.0.0.1:8080, so it is not exposed publicly. If another service already uses port 8080, choose another unused local port and use the same port in the later curl commands.

First, confirm that your Nginx configuration loads a directory where a small test server file can live:

sudo nginx -T 2>/dev/null | grep -E 'include .*(conf\.d|sites-enabled)'

Relevant output often includes one or both of these include paths:

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

Most packaged Nginx builds load /etc/nginx/conf.d/*.conf. If your source-built or custom configuration does not, place the test server block in a file that your nginx.conf already includes instead of blindly using the path below.

Create a temporary test file:

sudo nano /etc/nginx/conf.d/location-priority-test.conf

Paste this local-only server block:

server {
    listen 127.0.0.1:8080;
    server_name location-test.example;
    default_type text/plain;

    location = / {
        return 200 "exact root location\n";
    }

    location ^~ /assets/private/ {
        return 200 "priority prefix private assets\n";
    }

    location /assets/ {
        return 200 "normal assets prefix location\n";
    }

    location ~* \.(css|js|png|jpg|jpeg|gif|webp)$ {
        return 200 "regex static extension location\n";
    }

    location / {
        return 200 "catch-all prefix location\n";
    }
}

Test the Nginx configuration before reloading:

sudo nginx -t

A successful syntax test prints these lines:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Reload Nginx after the syntax test passes:

sudo systemctl reload nginx

The reload command normally returns no output on success. If your system does not use systemd, reload Nginx with the service manager used by that host.

Confirm that the temporary server is listening on the loopback port before sending requests:

sudo ss -ltnp | grep -F '127.0.0.1:8080'

The matching line should show Nginx bound to 127.0.0.1:8080. If it does not appear, recheck the syntax output and the include path you used for the test file.

Read Nginx Location Block Priority Results

Run the test requests from the Nginx host. The Host header selects the test server block without requiring DNS changes:

curl -s -H "Host: location-test.example" http://127.0.0.1:8080/
curl -s -H "Host: location-test.example" http://127.0.0.1:8080/assets/app.css
curl -s -H "Host: location-test.example" http://127.0.0.1:8080/assets/private/app.css
curl -s -H "Host: location-test.example" http://127.0.0.1:8080/assets/readme
curl -s -H "Host: location-test.example" http://127.0.0.1:8080/about

The output should appear in this order:

exact root location
regex static extension location
priority prefix private assets
normal assets prefix location
catch-all prefix location

The second line is the important lesson. The request for /assets/app.css first matches the normal prefix /assets/, but that prefix does not use ^~. Nginx then checks regex locations, finds the static extension regex, and uses the regex block instead.

The third line shows the opposite result. The request for /assets/private/app.css matches the longer prefix ^~ /assets/private/, so Nginx skips the static extension regex and uses the priority prefix block.

Apply Nginx Location Block Priority in Real Sites

Real Nginx configurations usually mix static files, application routes, reverse proxies, and extension handlers. Start with path ownership first, then decide whether each path should be allowed to fall through to regex locations.

Use Exact Nginx Locations for One URI

Use exact locations for endpoints that should not be affected by broader prefix or regex blocks:

location = /health {
    access_log off;
    return 200 "ok\n";
}

location = /favicon.ico {
    log_not_found off;
    access_log off;
    root /var/www/example/static;
}

An exact location for /health does not match /health/ or /health/check. Add a separate prefix or redirect if those paths should behave the same way.

Use Nginx Priority Prefix Locations for Directories That Must Bypass Regex

Use ^~ when a directory should stay in that prefix block even if a later regex could match the URI. Upload directories are a common example because uploaded files should usually be served as files, not executed by a PHP or application handler.

location ^~ /uploads/ {
    root /var/www/example/public;
    try_files $uri =404;
}

This block prevents a request such as /uploads/avatar.php from reaching a later location ~ \.php$ block, as long as ^~ /uploads/ is the longest matching prefix for that URI.

Order Nginx Regex Locations from Specific to Broad

Regex locations with ~ and ~* are checked in configuration order. Put narrow patterns before broad patterns when both can match the same request.

location ~ ^/api/v1/reports/.*\.json$ {
    proxy_pass http://127.0.0.1:3001;
}

location ~ ^/api/ {
    proxy_pass http://127.0.0.1:3000;
}

If the broad ^/api/ regex appears first, it catches /api/v1/reports/file.json before the more specific reports regex has a chance to run. Regex blocks are not sorted by detail or length.

Use Named Nginx Locations for Internal Fallbacks

A named location starts with @. It does not match a normal browser URL. Use it as a fallback target from directives such as try_files or error_page.

location / {
    try_files $uri $uri/ @app;
}

location @app {
    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;
}

The browser still requests /about or /dashboard. Nginx reaches @app internally only after try_files cannot find a matching static file or directory.

If this fallback proxies an application, the broader Nginx reverse proxy workflow covers proxy headers, upstreams, WebSocket handling, and HTTPS. If the fallback rewrites or redirects URLs instead, use the dedicated guides for Nginx rewrite rules and Nginx redirects.

Avoid Common Nginx Location Block Priority Mistakes

Most location bugs come from assuming one of three wrong rules: that Nginx checks every block from top to bottom, that a normal prefix always beats regex, or that the query string affects location selection.

MistakeWhat Actually HappensFix
Putting a broad regex above a specific regexThe first matching regex wins, even when a later regex is more specific.Move the specific regex above the broad regex or replace the regex with a prefix route.
Expecting location /assets/ to beat all image or CSS regex blocksA normal prefix can be overridden by a later regex match.Use location ^~ /assets/ only when the whole path should skip regex handling.
Using ^~ on a parent path but expecting it to beat a longer child prefixNginx first finds the longest prefix. A shorter ^~ prefix does not automatically beat a longer normal prefix.Put ^~ on the exact directory that should own the request path.
Expecting /api and /api/ to behave the same in every locationThey are different URIs. Slash-ending pass locations may redirect the slashless URI, while exact locations require separate blocks.Add an exact location when the slashless URI needs a deliberate redirect or a separate response.
Trying to browse to /@appNamed locations are internal and are not selected by normal request matching.Reach named locations through try_files, error_page, or another internal redirect.
Changing a query string to test location priorityNginx location matching uses the URI path, not query arguments.Test with different paths, or handle query logic in the application or a separate map-driven rule.

Trailing slashes also matter. location /api/ matches /api/users, but /api is a different URI. When a slash-ending prefix proxies or passes requests upstream, Nginx can automatically send a permanent redirect from the slashless URI to the slash-added URI. Add an exact location when you want to make that behavior explicit or handle /api differently:

If this route is a reverse proxy, the Nginx proxy_pass trailing slash guide covers what happens to the upstream URI after the location block is selected.

location = /api {
    return 301 /api/;
}

location /api/ {
    proxy_pass http://127.0.0.1:3000;
}

This makes the boundary explicit and keeps the application route under the prefix that actually owns /api/.

Troubleshoot Nginx Location Block Priority Problems

When a request reaches the wrong handler, prove three things in order: the request is reaching the expected server block, the expected location is eligible to match, and no regex or fallback is taking over later.

Inspect Active Nginx Location Blocks

Use the full Nginx configuration dump to inspect the merged runtime configuration. This catches included files that are easy to miss when you only open one site file:

sudo nginx -T 2>/dev/null | grep -E 'server_name|listen|location|root|alias|try_files|proxy_pass|fastcgi_pass|return'

Relevant lines may look like this:

listen 80;
server_name example.com www.example.com;
root /var/www/example/public;
location / {
try_files $uri $uri/ @app;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
location @app {
proxy_pass http://127.0.0.1:3000;

If the request is reaching the wrong server block, fix listen, server_name, or the default server first. Location priority only applies after Nginx has already selected the virtual server for the request.

Test the Nginx Location With a Host Header

Use the curl command with an explicit Host header to test the selected site from the Nginx server:

curl -I -H "Host: example.com" http://127.0.0.1/assets/app.css

Relevant headers include the status code and any headers added by the winning location:

HTTP/1.1 200 OK
Content-Type: text/css

If the output shows a redirect, proxy response, PHP error, or unexpected content type, compare the URI against each prefix and regex block. A header added only inside one location is often the fastest way to prove which block won.

Fix Static Files Handled by the Wrong Nginx Location

Static files often go wrong when a normal prefix is supposed to own a directory, but a regex extension handler overrides it. For example, this normal prefix does not stop the later regex:

location /assets/ {
    root /var/www/example/public;
}

location ~* \.(css|js|png|jpg|jpeg|gif|webp)$ {
    root /var/www/example/public;
    expires 30d;
}

If the whole /assets/ tree should stay in the prefix block, make the longest intended prefix a ^~ prefix:

location ^~ /assets/ {
    root /var/www/example/public;
    try_files $uri =404;
}

Do not add ^~ mechanically to every directory. If you rely on extension regex blocks to set cache headers or run a PHP handler, a ^~ prefix can intentionally block that regex behavior.

Fix a Correct Nginx Location With the Wrong File Path

Sometimes the right location wins, but the file lookup is still wrong. That is usually a root, alias, or try_files problem rather than a location priority problem.

location /downloads/ {
    root /var/www/example;
}

location /media/ {
    alias /mnt/media/;
}

With root, Nginx appends the URI to the root path. With alias, Nginx replaces the matching location prefix with the alias path. If the selected location is right but files still return 404, check the final filesystem path before changing priority modifiers.

For broader missing-file or missing-route troubleshooting, use the Nginx 404 Not Found guide after you confirm the winning location is the one you intended.

Clean Up the Nginx Location Block Priority Test

Remove the temporary local-only test server when you finish the examples:

sudo rm -f /etc/nginx/conf.d/location-priority-test.conf

Test and reload Nginx again:

sudo nginx -t && sudo systemctl reload nginx

A successful cleanup leaves the main configuration valid:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If you placed the test server in a different included site file, remove that file or delete only the temporary server block you added. Do not remove a live production server block as part of the cleanup.

Conclusion

Nginx location routing is easier to debug when exact matches, longest prefixes, priority prefixes, regex order, and named fallbacks each have a clear job. Keep high-risk routing changes behind nginx -t, a controlled reload, and local curl probes before changing a live server block.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show more of our fresh Linux tutorials in Top Stories and From your sources when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<blockquote>quote</blockquote> quote block

Got a Question or Feedback?

We read and reply to every comment - let us know how we can help or improve this guide.

Let us know you are human: