Writing Nginx Regex Redirects for Query Strings
Problem Statement
You need Nginx redirects that match on or transform query-string parameters during a migration — for example mapping ?id=42 to a clean path, preserving utm_* trackers, or dropping a legacy parameter. Nginx does not match the query string inside a location regex; you have to use $args, $arg_<name>, or a map block. This page sits under Regex Redirect Rules.
When to Use This Approach
- Old URLs carry the page identity in a query parameter (
?id=,?p=,?page=). - You must preserve tracking parameters (
utm_source,gclid) across the redirect. - You need to strip a deprecated parameter while keeping the rest.
- A
location ~regex is silently ignoring the query string and you are not sure why. - You want a maintainable parameter-to-path map rather than dozens of
ifblocks.
Step-by-Step Instructions
1. Preserve the Whole Query String
A location regex matches only the path. To carry the query string onto the target, append $is_args$args — $is_args is ? when args exist and empty otherwise, so you never emit a dangling ?.
# Path rename that keeps every original query parameter
location ~* ^/old-category/(.*)$ {
return 301 /new-category/$1$is_args$args;
}
2. Redirect Based on a Single Parameter
Nginx exposes each parameter as $arg_<name>. Match on it with a map (cleaner than nested if), then redirect only when it is present.
# Map a legacy ?id= value to a clean path
map $arg_id $id_target {
default "";
42 /products/dns-monitor;
43 /products/ttl-tuner;
}
server {
location = /product.php {
if ($id_target) { return 301 $id_target; }
}
}
3. Strip a Deprecated Parameter, Keep the Rest
To drop one parameter you must rebuild $args without it. Use a map over $args for the common cases, or normalise the full query string to a canonical form.
# Remove a legacy sessionid param while preserving other args
if ($args ~ (.*)(?:^|&)sessionid=[^&]*(.*)) {
set $clean_args $1$2;
return 301 $uri?$clean_args;
}
4. Collapse a Parameterised Variant to a Canonical URL
When several parameter combinations should resolve to one canonical page, map them all to the same target and drop the query string entirely.
# Old paginated/sorted variants all canonicalise to the clean category page
map $arg_sort $sort_seen { default 0; "" 0; }
location = /category {
if ($arg_sort) { return 301 /category; } # drop ?sort=, keep one canonical URL
}
Worked Example
Legacy product pages used ?id=. The goal is a clean path that still preserves campaign trackers.
Before:
$ curl -sI 'https://www.example.com/product.php?id=42&utm_source=newsletter'
HTTP/2 200
With a map that captures id and re-appends remaining args, the request resolves to the clean path while keeping utm_source:
$ curl -sIL 'https://www.example.com/product.php?id=42&utm_source=newsletter' \
| grep -iE '^HTTP|^location'
HTTP/2 301
location: https://www.example.com/products/dns-monitor?utm_source=newsletter
HTTP/2 200
The page identity moves from query string to path in a single hop, and the campaign parameter survives.
Verification
- Confirm the parameter-driven redirect and final status:
curl -sIL 'https://www.example.com/product.php?id=42' | grep -iE '^HTTP|^location'. - Test tracker preservation by requesting a URL with
utm_sourceand confirming it appears on theLocation. - Test the strip rule by sending the deprecated parameter and confirming it is absent from the target while other args remain.
FAQ
Why does my location regex ignore the query string?
Because Nginx location matching only sees the normalised path — the query string is never part of the regex. Match parameters with $arg_<name> or a map over $args instead, and append $is_args$args to carry them onto the target.
Should I use map or if for query-string redirects?
Prefer map — it is evaluated once, is faster, and avoids the well-known pitfalls of if inside location. Reserve if for the narrow case of conditionally rebuilding $args, as in the strip example.
Related
← Back to Regex Redirect Rules