Introduction
Nginx location blocks use regex patterns to route requests, but a common pitfall is that query strings are not included in the URI that location blocks match against. When a regex location block seems to fail for URLs with query parameters, the issue is usually a misunderstanding of how Nginx processes request URIs. The URI passed to location matching is /path/to/resource, not /path/to/resource?key=value.
Symptoms
- Regex location
~ /api/v1/.*works for/api/v1/usersbut not for/api/v1/users?expand=true - Request falls through to the default
location /block instead of the intended regex block - Query string parameters appear to be ignored by rewrite rules
- Confusing behavior where some paths match and others with the same path plus query string do not
Common Causes
- Nginx location blocks match against
$urionly, not$request_uriwhich includes the query string - Query strings contain characters that interfere with regex patterns (e.g.,
?,&,=) - Percent-encoded characters in the URI (e.g.,
%20for space) not matching the expected regex - Confusion between
=(exact match),~(case-sensitive regex), and~*(case-insensitive regex) modifiers
Step-by-Step Fix
- 1.Understand that location blocks do not see query strings. The URI
/api/v1/users?expand=trueis matched as/api/v1/usersonly. If you need to match based on query string presence, use anifblock ormapdirective: - 2.```nginx
- 3.server {
- 4.# This matches /api/v1/users regardless of query string
- 5.location ~ ^/api/v1/ {
- 6.proxy_pass http://backend;
- 7.}
- 8.}
- 9.
` - 10.Use map to route based on query string presence:
- 11.```nginx
- 12.map $arg_expand $backend_pool {
- 13.default app_pool;
- 14."true" expanded_pool;
- 15.}
server {
location /api/v1/ {
proxy_pass http://$backend_pool;
}
}
``
The $arg_expand variable automatically extracts the expand` query parameter.
- 1.Use if for query-string-based rewrites (the only safe use of if in location context):
- 2.```nginx
- 3.location /api/v1/ {
- 4.if ($args ~ "format=csv") {
- 5.rewrite ^ /api/v1/export last;
- 6.}
- 7.proxy_pass http://backend;
- 8.}
- 9.
` - 10.Handle percent-encoded URIs properly. Use
~*for case-insensitive matching, which also helps with encoded characters: - 11.```nginx
- 12.location ~* ^/files/[\w%-]+\.(pdf|docx?)$ {
- 13.alias /var/www/files/;
- 14.}
- 15.
` - 16.This matches
/files/report%202024.pdfand/files/annual-report.pdf. - 17.Debug matching with Nginx error log at debug level:
- 18.```nginx
- 19.# In nginx.conf, change error_log level
- 20.error_log /var/log/nginx/debug.log debug;
- 21.
` - 22.Then check which location block handles each request:
- 23.```bash
- 24.grep "test location" /var/log/nginx/debug.log
- 25.
`
Prevention
- Always test location regex patterns with
curl -vto verify which block handles the request - Use
mapdirectives instead ofifblocks when possible for query-string-based routing - Keep a test suite of URLs (with and without query strings) to validate location configurations after changes
- Document which variables are available in which Nginx contexts (
$urivs$request_urivs$args) - Avoid regex in location blocks when prefix matching suffices - prefix matching is faster and less error-prone