When working with Nginx as a reverse proxy, we often need to manipulate URLs before passing them to backend servers. A common requirement is appending additional parameters to existing query strings. In this case, we need to append &locale=de-de
but only when:
- The URL contains a query string (has a ? character)
- The locale parameter isn't already present
The basic rewrite rule:
rewrite ^(.*)$ $1&locale=de-de break;
Has several issues:
- Appends the parameter even when there's no query string
- Doesn't check for existing locale parameter
- May cause duplicate parameters
Here's the correct approach using Nginx's regex capabilities:
if ($args ~ "^((?!.*locale=).)*$") {
rewrite ^(.*)$ $1$is_args$args&locale=de-de break;
}
This solution:
- Checks if 'locale=' is NOT present in arguments
- Uses $is_args to properly handle the ? character
- Preserves existing query parameters
When using proxy_pass, Nginx handles URL encoding differently. The proper way to maintain the question mark is:
location / {
proxy_pass http://backend$request_uri$is_args$args;
# Additional proxy settings...
}
Key points:
$is_args
will be "?" if args exist, empty otherwise$request_uri
includes the original URI with encoding preserved- This prevents the %3F encoding issue
Here's a full configuration example that solves both problems:
server {
listen 80;
server_name example.com;
location / {
# Check if locale parameter is missing and URL has query string
if ($args ~ "^((?!.*locale=).)*$") {
rewrite ^(.*)$ $1$is_args$args&locale=de-de break;
}
# Proper proxy pass with preserved question mark
proxy_pass http://backend$request_uri$is_args$args;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Test cases to verify your setup works correctly:
Input URL | Expected Output |
---|---|
/path | /path (no change) |
/path?param=1 | /path?param=1&locale=de-de |
/path?locale=en-us | /path?locale=en-us (no change) |
/path?param=1&locale=fr-fr | /path?param=1&locale=fr-fr (no change) |
When working with Nginx rewrite rules, a common requirement is to conditionally append query parameters while avoiding duplicates. The specific case we're examining involves adding &locale=de-de
to URLs, but only when:
- The parameter doesn't already exist
- The URL contains a query string (has a ? character)
The initial approach:
rewrite ^(.*)$ $1&locale=de-de break;
has several problems:
- It adds the parameter unconditionally
- It doesn't check for existing parameters
- It may create malformed URLs when no query string exists
Here's a more sophisticated solution using Nginx's if
directive and regex pattern matching:
if ($args !~* "locale=") {
rewrite ^([^?]*\?)(.*)$ $1$2&locale=de-de? last;
rewrite ^([^?]*)$ $1?locale=de-de? last;
}
This solution:
- First checks if 'locale=' already exists in query parameters
- For URLs with existing query strings (? present), appends the parameter
- For URLs without query strings, adds the parameter as the first one
The behavior you're seeing with $uri?$args
and $uri$is_args$args
relates to Nginx's internal URI processing:
# This works correctly:
rewrite ^/oldpath /newpath?param=value;
# These might show encoding issues:
proxy_pass http://backend$uri?$args;
proxy_pass http://backend$uri$is_args$args;
The solution is to properly construct the URL before passing to proxy_pass:
set $args $args&locale=de-de;
proxy_pass http://backend$uri$is_args$args;
Here's a full server block demonstrating the solution:
server {
listen 80;
server_name example.com;
location / {
if ($args !~* "locale=") {
set $args $args&locale=de-de;
}
proxy_set_header Host $host;
proxy_pass http://backend$uri$is_args$args;
}
}
When implementing this solution:
- Be cautious with if directives in Nginx - they have some limitations
- Test thoroughly with various URL patterns
- Consider edge cases like URLs ending with ? but no parameters
- Remember that $args modification affects the entire request
For more complex scenarios, consider using Nginx's map directive:
map $args $new_args {
default "$args&locale=de-de";
"~*locale=" "$args";
}
server {
...
location / {
set $args $new_args;
proxy_pass http://backend;
}
}