Nginx Rewrite: Properly Append &locale Parameter to URLs with Query Strings


3 views

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:

  1. Checks if 'locale=' is NOT present in arguments
  2. Uses $is_args to properly handle the ? character
  3. 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:

  1. It adds the parameter unconditionally
  2. It doesn't check for existing parameters
  3. 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;
        }
    }