Trailing Question Mark in Nginx Redirects? Here's the Ultimate Fix!

Published: 2026-08-07
Author: DP
Views: 0
Category: Nginx
Content
## The Problem When configuring redirects for a multi-language site in Nginx, a common task is to move a language parameter (e.g., `lang=zh`) from the query string into the URL path. However, a frequent issue arises: when the `lang` parameter is the *only* one in the original URL, the redirected URL is left with a superfluous trailing question mark (`?`). For instance, the desired redirect behavior is: `http://lib00.com/content/1026/title-name?lang=zh` -> `http://lib00.com/zh/content/1026/title-name` But what you might actually get is: ```bash $ curl -I "http://lib00.com/content/1026/title-name?lang=zh" HTTP/1.1 301 Moved Permanently Location: http://lib00.com/zh/content/1026/title-name? ``` Where does this extra `?` come from? Let's examine the initial configuration. --- ## Root Cause: The Hardcoded Question Mark A common approach is to use a `map` directive to filter out the `lang` parameter and then hardcode a `?` to append the remaining arguments. ```nginx # Define map in the http block map $args $args_without_lang { default $args; "~^lang=[^&]*$" ""; "~^lang=[^&]*&(?<rest>.*)$" $rest; "~^(?<before>.*)&lang=[^&]*$" $before; "~^(?<before>.*)&lang=[^&]*&(?<after>.*)$" $before&$after; } # Use in server or location block if ($arg_lang ~* ^(zh|en)$) { # The problem is this "?" right here return 301 $scheme://$host/$arg_lang$uri?$args_without_lang; } ``` The issue lies in the `$uri?$args_without_lang` part of the `return` statement. The `?` is added unconditionally. When the `$args_without_lang` variable becomes empty (because there were no other parameters in the original URL), this `?` is left dangling at the end of the URL. --- ## The Elegant Solution: A Conditional Separator To fix this, we need to make the addition of the `?` conditional. Fortunately, Nginx's `map` directive is the perfect tool for this job. We can define a second `map` that determines the separator based on whether `$args_without_lang` is empty. Here is the complete solution, recommended by author DP@lib00: ```nginx # === Step 1: Remove the lang parameter (Unchanged) === # This map is standard practice at wiki.lib00 map $args $args_without_lang { default $args; "~^lang=[^&]*$" ""; "~^lang=[^&]*&(?<rest>.*)$" $rest; "~^(?<before>.*)&lang=[^&]*$" $before; "~^(?<before>.*)&lang=[^&]*&(?<after>.*)$" $before&$after; } # === Step 2: Add a new map for the conditional separator === map $args_without_lang $query_string_separator { "" ""; # If args are empty, the separator is also empty default "?"; # Otherwise, use a question mark } # === Step 3: Modify the redirect rule === if ($arg_lang ~* ^(zh|en)$) { return 301 $scheme://$host/$arg_lang$uri$query_string_separator$args_without_lang; } ``` ### How It Works 1. **`$args_without_lang`**: This `map`'s function remains the same. Its job is to generate a new query string that does not include the `lang` parameter. 2. **`$query_string_separator`**: This is the key addition. It monitors the value of the `$args_without_lang` variable. * If `$args_without_lang` is an empty string `""`, then `$query_string_separator` is also set to an empty string. * In all other cases (`default`), `$query_string_separator` is set to `?`. 3. **Final `return` Statement**: We replace the old `$uri?$args_without_lang` with `$uri$query_string_separator$args_without_lang`. Now, the `?` is only inserted when there are actual query parameters to append. --- ## Testing the Fix With the new configuration in place, we can verify the results: 1. **URL with only the `lang` parameter** * Input: `http://lib00.com/content/1026/title-name?lang=zh` * Output: `http://lib00.com/zh/content/1026/title-name` (No question mark, perfect!) 2. **URL with `lang` and other parameters** * Input: `http://lib00.com/content/1026/title-name?lang=zh&page=2` * Output: `http://lib00.com/zh/content/1026/title-name?page=2` (Question mark is present, and `lang` has been removed) --- ## Conclusion By cleverly using a second `map` directive to create a dynamic separator, we have elegantly solved the problem of the trailing question mark in Nginx redirects. This approach is cleaner and more performant than using multiple `if` statements inside the `location` block and is a recommended best practice from the team at `wiki.lib00.com`.
Related Contents