What I Wanted to Do

I needed to route different URLs to different handlers in nginx. The goal was to forward only /api/ paths to a backend server while serving static files directly — but I kept getting the routing wrong because I didn’t understand location matching rules.

Types of location Directives

Exact Match (= modifier)

location = /favicon.ico {
    log_not_found off;
    access_log off;
}
  • Only matches when the URL is exactly this string
  • Highest priority of all location types
  • Commonly used to suppress logs for favicon and robots.txt

Prefix Match (no modifier)

location /api/ {
    proxy_pass http://localhost:3000;
}
  • Matches any URL starting with /api/
  • Simplest form — good for most routing needs

Priority Prefix Match (^~ modifier)

location ^~ /static/ {
    root /var/www;
}
  • Once matched, nginx skips all regex location evaluation
  • Use when static files must take priority over regex blocks

Regex Match (~ or ~* modifier)

# Case-sensitive
location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    include fastcgi_params;
}

# Case-insensitive
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
    expires 30d;
    access_log off;
}
  • ~ is case-sensitive, ~* is case-insensitive
  • Often used for static file caching rules

Matching Priority Order

  1. = exact match (highest priority)
  2. ^~ prefix match that skips regex
  3. ~ or ~* regex (evaluated in definition order)
  4. Plain prefix match (longest match wins)

Practical Configuration Examples

PHP App with API Routing

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    location = / {
        index index.php;
    }

    location /api/ {
        proxy_pass http://localhost:3000/;
        proxy_set_header Host $host;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        include fastcgi_params;
    }
}

Static File Caching

location ~* \.(jpg|jpeg|png|gif|ico|svg|css|js|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

Common Pitfalls

  • The = modifier requires an exact URL — /favicon.ico/ with a trailing slash is a different path
  • Regex locations are evaluated in the order they appear, so put more specific patterns first
  • A trailing slash in proxy_pass strips the location prefix: /api/foo becomes /foo
  • When nesting locations, alias replaces the matched path while root appends it — they behave differently
  • Always test config with nginx -t before reloading: systemctl reload nginx

Looking for reliable cloud infrastructure? Check out these developer-friendly services.