NGINX

NGINX Location Priority: Complete Regex Matching Guide

by , , revisited on


We have by far the largest RPM repository with NGINX module packages and VMODs for Varnish. If you want to install NGINX, Varnish, and lots of useful performance/security software with smooth yum upgrades for production use, this is the repository for you.
Active subscription is required.

Understanding how NGINX routes requests to different location blocks is essential for anyone configuring web servers. The location directive is one of the most powerful yet frequently misunderstood features in NGINX configuration. Getting NGINX location priority wrong can break your application, create security vulnerabilities, or cause subtle bugs that are difficult to diagnose.

This comprehensive guide explains how NGINX location priority actually works, backed by insights from the NGINX source code. You’ll learn the exact matching algorithm, understand when to use each modifier, and discover common pitfalls that even experienced administrators fall into.

The Five Location Modifiers

NGINX provides five ways to define a location block, each with different matching behavior. Understanding these modifiers is the first step to mastering NGINX location priority:

Modifier Type Priority Level Description
= Exact match Highest Request URI must match exactly
^~ Prefix (no regex) High Prefix match that prevents regex evaluation
~ Regex (case-sensitive) Medium Regular expression with case-sensitive matching
~* Regex (case-insensitive) Medium Regular expression ignoring case
(none) Prefix Lower Standard prefix match

Exact Match (=) — Highest Priority

The exact match modifier = requires the request URI to match the location precisely. In the NGINX location priority order, exact matches are always checked first and return immediately.

# Only matches exactly "/"
location = / {
    return 200 "Homepage";
}

# Only matches exactly "/login"
location = /login {
    proxy_pass http://auth-backend;
}

With exact match:
/ matches location = / but /index.html does not
/login matches location = /login but /login/ does not

Prefix Match with No Regex (^~)

The ^~ modifier affects NGINX location priority by telling NGINX to use this location if the prefix matches, and skip all regex locations entirely. This is critical for performance-sensitive paths where you don’t want regex evaluation overhead.

# Matches /images/ and all paths starting with /images/
# Regex locations will NOT be checked even if they match
location ^~ /images/ {
    root /var/www/static;
}

# This regex will NEVER match URLs starting with /images/
location ~ \.(gif|jpg|png)$ {
    expires 30d;
}

When a request for /images/photo.jpg arrives:
1. NGINX finds the ^~ prefix match for /images/
2. Because of ^~, it stops and uses this location
3. The regex \.(gif|jpg|png)$ is never evaluated

Case-Sensitive Regex (~)

The tilde ~ modifier enables regular expression matching with case sensitivity. In the NGINX location priority algorithm, regex locations are evaluated after prefix matching (unless ^~ is used).

# Matches .php at the end (case-sensitive)
location ~ \.php$ {
    fastcgi_pass unix:/run/php-fpm/www.sock;
}

This location matches /index.php but not /INDEX.PHP or /index.PHP.

For PHP-FPM configuration, see our guide on NGINX FastCGI Keepalive for optimal performance.

Case-Insensitive Regex (~*)

The ~* modifier enables regular expression matching while ignoring case differences.

# Matches .jpg, .JPG, .Jpg, etc.
location ~* \.(jpg|jpeg|gif|png|webp)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

For more on caching headers, see our comprehensive NGINX Browser Caching Guide.

Standard Prefix Match (No Modifier)

Without any modifier, NGINX performs a standard prefix match. This has lower priority in NGINX location priority than ^~ and allows regex locations to override the match.

# Matches /api/ and anything starting with /api/
location /api/ {
    proxy_pass http://api-backend;
}

For proxy configuration details, check our NGINX Reverse Proxy Guide.

The Complete NGINX Location Priority Algorithm

The matching algorithm follows a specific order that determines which location block handles each request. Understanding this NGINX location priority order is crucial for correct configuration.

The Official Priority Order

  1. Exact match (=) — If found, use it immediately and stop
  2. Longest prefix match — Find the longest matching prefix (with or without ^~)
  3. If the longest prefix has ^~ — Use it and stop searching
  4. Regex locations (~ and ~*) — Check in order of definition; first match wins
  5. Use the remembered longest prefix — If no regex matched, fall back to prefix

Visualizing the Algorithm

Request: /images/photo.jpg

Step 1: Check exact matches (=)
        └─ No match found → continue

Step 2: Find longest prefix match
        ├─ location /           (length: 1)
        ├─ location /images/    (length: 8) ← longest
        └─ Remember this match

Step 3: Check if longest prefix has ^~
        └─ If yes → USE IT and stop
        └─ If no  → continue to regex

Step 4: Check regex locations IN ORDER
        ├─ location ~ \.php$           → no match
        ├─ location ~* \.(jpg|png)$    → MATCH!
        └─ Use first matching regex

Step 5: (Only if no regex matched)
        └─ Use the remembered prefix match

Real-World NGINX Location Priority Example

Consider this configuration:

server {
    listen 80;
    server_name example.com;

    location = / {
        return 200 "A: Exact root";
    }

    location / {
        return 200 "B: Prefix root";
    }

    location /api/ {
        return 200 "C: Prefix api";
    }

    location ^~ /static/ {
        return 200 "D: Prefix noregex static";
    }

    location ~ \.php$ {
        return 200 "E: Regex PHP";
    }

    location ~* \.(jpg|png|gif)$ {
        return 200 "F: Regex images";
    }
}

Here’s what matches each request based on NGINX location priority rules:

Request Matched Why
/ A Exact match takes highest priority
/index.html B Prefix / is longest, no regex matches
/api/users C Prefix /api/ is longest, no regex matches
/api/export.php E Prefix /api/ found, but regex \.php$ wins
/static/style.css D ^~ prefix prevents regex evaluation
/static/image.jpg D ^~ blocks the image regex too!
/photos/cat.jpg F Prefix / found, regex \.(jpg|png|gif)$ wins
/test.PHP B Regex \.php$ is case-sensitive, doesn’t match

How NGINX Implements Location Matching Internally

NGINX uses a balanced binary search tree for prefix locations and a linear array for regex locations. This internal implementation explains NGINX location priority performance:

  • Prefix matching is O(log n) — Very fast even with hundreds of locations
  • Regex matching is O(n) — Each regex is checked in order until one matches

The source code in ngx_http_core_module.c reveals the data structure:

struct ngx_http_location_tree_node_s {
    ngx_http_location_tree_node_t   *left;      // BST left subtree
    ngx_http_location_tree_node_t   *right;     // BST right subtree
    ngx_http_location_tree_node_t   *tree;      // Nested locations

    ngx_http_core_loc_conf_t        *exact;     // Exact match (=)
    ngx_http_core_loc_conf_t        *inclusive; // Prefix match

    u_short                          len;
    u_char                           name[1];   // Location path
};

NGINX builds this tree at configuration load time. The tree structure means:
– Location block order in your config doesn’t affect prefix matching
– The longest prefix always wins, regardless of definition order
– Only regex order matters — the first matching regex wins

Regex Best Practices

Always Anchor Your Patterns

One of the most common mistakes is forgetting to anchor regex patterns. Without anchors, patterns can match anywhere in the URI, creating security vulnerabilities.

Dangerous (unanchored):

# WRONG: Matches .php ANYWHERE in the URL
location ~ \.php {
    fastcgi_pass unix:/run/php-fpm/www.sock;
}

This matches:
/index.php ✓ (intended)
/index.phpx ✓ (unintended!)
/path/.php/evil ✓ (security risk!)

Safe (anchored):

# CORRECT: Matches .php only at the END
location ~ \.php$ {
    fastcgi_pass unix:/run/php-fpm/www.sock;
}

The $ anchor ensures the pattern only matches at the end of the URI.

Place More Specific Regex First

Since regex locations are evaluated in definition order, place more specific patterns before general ones:

# CORRECT ORDER: More specific patterns first
location ~ ^/api/v2/admin/.*\.json$ {
    # Most specific - checked first
    auth_basic "Admin API";
}

location ~ ^/api/v2/.*\.json$ {
    # Less specific - checked second
    proxy_pass http://api-v2;
}

location ~ ^/api/.*\.json$ {
    # Least specific - checked last
    proxy_pass http://api-legacy;
}

If you reverse this order, requests to /api/v2/admin/users.json would match the general /api/ pattern first.

Avoid Catastrophic Backtracking (ReDoS)

Certain regex patterns can cause exponential backtracking, leading to denial of service:

# DANGEROUS: Nested quantifiers cause ReDoS
location ~ ^/(a+)+$ {
    # This pattern can take exponential time on crafted input
}

# DANGEROUS: Overlapping alternatives
location ~ ^/(foo|foobar)+$ {
    # Can also cause catastrophic backtracking
}

Safe patterns avoid:
– Nested quantifiers like (a+)+ or (.*)*
– Overlapping alternatives like (a|ab)+
– Unbounded repetition of groups with optional elements

Security Considerations

The Alias Path Traversal Vulnerability

When using alias with prefix locations, always include a trailing slash on both the location and alias path:

# VULNERABLE: Missing trailing slash on location
location /files {
    alias /var/www/uploads/;
}
# Attack: /files../etc/passwd → /var/www/uploads/../etc/passwd
# SAFE: Trailing slash on both
location /files/ {
    alias /var/www/uploads/;
}

Avoid “if” Inside Location Blocks

The if directive inside location blocks is notorious for unexpected behavior. NGINX documentation explicitly warns: “If is Evil.”

# PROBLEMATIC: if inside location
location / {
    if ($request_uri ~* "\.php$") {
        proxy_pass http://php-backend;
    }
    proxy_pass http://default-backend;
}
# BETTER: Use separate location blocks
location ~ \.php$ {
    proxy_pass http://php-backend;
}

location / {
    proxy_pass http://default-backend;
}

The only safe directives inside if blocks are:
return
rewrite ... last
rewrite ... redirect
rewrite ... permanent

Named Locations

Named locations (prefixed with @) are special locations that can only be reached through internal redirects:

server {
    location / {
        try_files $uri $uri/ @backend;
    }

    location @backend {
        proxy_pass http://app-server;
    }
}

Named locations:
– Cannot be accessed directly by clients
– Are used by try_files, error_page, and internal redirects
– Don’t participate in the normal matching algorithm

Nested Locations

NGINX supports nested location blocks for complex configurations:

location /api/ {
    proxy_set_header X-Real-IP $remote_addr;

    location /api/public/ {
        proxy_pass http://public-api;
    }

    location /api/private/ {
        auth_basic "Private API";
        proxy_pass http://private-api;
    }
}

Rules for nested locations:
– Nested locations must be prefixes of the parent location
– Regex locations cannot contain nested locations
– Exact match (=) locations cannot contain nested locations
– Child locations inherit configuration from parents

Debugging Location Matching

Using Debug Logging

Enable debug logging to see exactly which location NGINX selects:

error_log /var/log/nginx/error.log debug;

The debug log shows the matching process:

[debug] 1234#0: *1 test location: "/"
[debug] 1234#0: *1 test location: "/api/"
[debug] 1234#0: *1 using configuration "/api/"

Testing with nginx -T

The nginx -T command outputs the complete configuration after processing includes:

nginx -T

Using curl for Testing

Test individual URLs to verify matching behavior:

curl -I http://localhost/api/users
curl -ILs http://localhost/old-path
curl -s http://localhost/test-endpoint

Validating Configuration with Gixy

Gixy is a static analyzer for NGINX configurations that detects security vulnerabilities and common misconfigurations:

  • Unanchored regex patterns
  • Alias path traversal vulnerabilities
  • HTTP response splitting
  • Host header spoofing
  • ReDoS patterns

Installing Gixy

pip3 install gixy

Running Gixy

gixy /etc/nginx/nginx.conf
gixy -f json /etc/nginx/nginx.conf

Example output:

==================== Results ===================

>> Problem: [alias_traversal] Path traversal via misconfigured alias.
Severity: HIGH

server {
    location /i {
        alias /data/images/;
    }
}

==================== Summary ===================
Total issues:
    High: 1

For continuous monitoring with automated security analysis, GetPageSpeed Amplify integrates Gixy checks along with performance metrics and SSL certificate monitoring.

Performance Optimization Tips

Use ^~ for Static Files

When serving static files from a specific path, use ^~ to skip regex evaluation:

location ^~ /static/ {
    root /var/www;
    expires 1y;
}

location ~* \.(css|js|jpg|png|gif)$ {
    expires 30d;
}

Prefer Prefix Over Regex When Possible

Prefix matching uses binary search (O(log n)), while regex uses linear search (O(n)):

# FASTER: Prefix match
location /api/v1/ {
    proxy_pass http://api-v1;
}

# SLOWER: Regex match
location ~ ^/api/v[0-9]+/ {
    proxy_pass http://api-generic;
}

Use Exact Match for High-Traffic Endpoints

For frequently accessed URLs, exact match provides the fastest lookup:

location = / {
    proxy_pass http://homepage-backend;
}

location = /health {
    access_log off;
    return 200 "OK";
}

For rate limiting, see our NGINX Rate Limiting Guide.

Common Configuration Patterns

PHP-FPM with Security

location ~ /\. {
    deny all;
}

location ~ ^/index\.php(/|$) {
    fastcgi_pass unix:/run/php-fpm/www.sock;
    fastcgi_split_path_info ^(.+\.php)(/.*)$;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param DOCUMENT_ROOT $document_root;
    internal;
}

location ~ \.php$ {
    return 404;
}

Static Files with Caching

location ^~ /assets/ {
    root /var/www;
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ {
    expires 30d;
    add_header Cache-Control "public";
}

API Gateway Pattern

location /api/v3/ {
    proxy_pass http://api-v3;
}

location /api/v2/ {
    proxy_pass http://api-v2;
}

location /api/v1/ {
    return 301 /api/v2$request_uri;
}

location /api/ {
    proxy_pass http://api-v3;
}

Summary

Understanding NGINX location priority is essential for building secure and performant web configurations. Remember these key points:

  1. Exact match (=) has highest priority and is the fastest
  2. Longest prefix is found first, regardless of config order
  3. ^~ prefix stops regex evaluation completely
  4. Regex locations are checked in order — first match wins
  5. Always anchor regex patterns with ^ and $ to prevent security issues
  6. Use Gixy to validate your configuration for security vulnerabilities
  7. Prefer prefix over regex when possible for performance

The location directive’s flexibility is powerful, but with that power comes responsibility. Take time to understand the NGINX location priority algorithm, test your configurations thoroughly, and use tools like Gixy to catch potential issues before they reach production.

For more NGINX configuration guides, explore our articles on NGINX load balancing and TLS hardening.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

This site uses Akismet to reduce spam. Learn how your comment data is processed.