Nginx

Nginx Subfolder Redirect Losing Query String Parameters

· by DebuggedIt

Quick answer

A redirect that drops query string parameters happens because Nginx doesn't automatically forward them to the redirect target — unlike some other systems where...

A redirect that drops query string parameters happens because Nginx doesn't automatically forward them to the redirect target — unlike some other systems where query strings might be preserved by default, Nginx's return and rewrite directives require you to explicitly include the query string in the target URL if you want it carried through.

The Problem

A redirect configured to send visitors from one path to another works for the base path, but any query parameters on the original request disappear:

location /old-path {
    return 301 /new-path;
}

Visiting /old-path?utm_source=newsletter&ref=123 redirects to just /new-path, with the utm_source and ref parameters silently dropped, which is a problem if those parameters carry information (tracking, filters, pagination state) that the destination page actually needs.

Why It Happens

Nginx's return directive (and rewrite, similarly) treats the target URL exactly as written — if the target doesn't include a reference to the original request's query string, none is automatically appended. This is different behavior from some other web servers or frameworks that might preserve query strings by default, and needs to be handled explicitly in Nginx configuration whenever you want a redirect to carry through the original request's parameters.

The Fix

Explicitly append the original query string to the redirect target using Nginx's $is_args$args variable combination, which correctly handles both the case where query parameters exist and where they don't:

location /old-path {
    return 301 /new-path$is_args$args;
}

$is_args evaluates to ? if the original request had any query string, or an empty string if it didn't — and $args contains the actual query string content itself. Using both together correctly reconstructs the query string portion of the URL only when one actually existed, avoiding an incorrect trailing ? on requests that had no parameters to begin with.

An alternative, equally correct approach uses just $args with an explicit ?, though this requires being careful about the edge case where no query string exists at all:

location /old-path {
    return 301 /new-path?$args;
}

This works correctly for requests that do have a query string, but produces a redirect with a trailing bare ? for requests that didn't originally have one — $is_args$args is generally the more robust, cleaner choice specifically because it avoids this minor but sometimes noticeable edge case.

For rewrite directives specifically, the same principle applies — the query string needs to be explicitly referenced, though rewrite has slightly different default behavior worth understanding precisely:

rewrite ^/old-path$ /new-path permanent;
# by default, rewrite actually DOES append the original query string automatically,
# UNLESS the rewritten target itself already contains a "?"

rewrite's behavior differs subtly from return here — it does automatically append the original query string, but only if the target URL you specify doesn't already include its own ?. If your rewrite target needs to add its own new query parameters in addition to preserving the original ones, you need to explicitly combine both:

rewrite ^/old-path$ /new-path?additional_param=value&$args permanent;

For redirects involving more complex path transformations (not just a simple path swap, but also restructuring based on captured groups), ensure the query string handling is included alongside whatever capture-group-based rewriting you're already doing:

location ~ ^/old-path/(?<id>\d+)$ {
    return 301 /new-path/$id$is_args$args;
}

Still Not Working?

If you've added $is_args$args but still see parameters dropped for specific requests, check whether those specific parameters are being stripped earlier in the request chain — by a CDN, a WAF, or another proxy layer in front of Nginx that might have its own query parameter filtering or normalization rules, independent of anything configured in Nginx itself. Testing the redirect by connecting directly to Nginx (bypassing any earlier layers, if that's feasible in your setup) isolates whether the parameter loss is genuinely happening within your Nginx configuration or somewhere earlier in the request's path before it even reaches Nginx.