Score:0

Nginx redirect based on query string

in flag

I'm migrating old website links to new website, it will use same domain

Here is some old links.

Old Links New links
http://example.com/?p=contact /contact
http://example.com/?p=static&id=career /career
http://example.com/?p=static&id=about /about
http://example.com/?p=catalog&action=images&cat_id=1 /product-category/category-slug-1
http://example.com/?p=catalog&action=images&cat_id=2 /product-category/category-slug-2
http://example.com/?p=catalog&action=viewimages&pid=1&cat_id=1 /product/product-slug-1
http://example.com/?p=catalog&action=viewimages&pid=2&cat_id=3 /product/product-slug-2

The new product page doesn't have id in the URL, so I list all of them manually, and they are just 5 categories and 20 product pages in total.

This is what I've tried before I knew that nested if is not supported.

location / {
    if ($arg_p = contact) { return 301 /contact; }
    if ($arg_p = static) { 
        if ($arg_id = career) { return 301 /career; }
        # other static pages redirect to /about
        return 301 /about;
    }

    if ($arg_p = catalog) {
        if ($arg_action = images) {
            if ($arg_cat_id = 1) { return 301 /product-category/category-slug-1; }
            if ($arg_cat_id = 2) { return 301 /product-category/category-slug-2; }
            # other unlisted categories should redirect to /product-categories
            return 301 /product-categories;
        }
        if ($arg_action = viewimages) {
            if ($arg_pid = 1) { return 301 /product/product-slug-1/; }
            if ($arg_pid = 2) { return 301 /product/product-slug-2/; }
        }
        # other unlisted links defaults to /products
        return 301 /products;
    }
}

What is the config should be?

Score:2
gr flag

You can do it with several chained map blocks. Here is an idea:

map $arg_p $url_p {
    contact    /contact;
    static     $url_id;
    catalog    $url_action;
    # default value will be an empty string
}

map $arg_id $url_id {
    career     /career;
    about      /about;
    # other static pages redirect to /about
    default    /about;
}

map $arg_action $url_action {
    images     $url_cat_id;
    viewimages $url_pid;
    # other unlisted actions defaults to /products
    default    /products;
}

map $arg_cat_id $url_cat_id {
    1          /product-category/category-slug-1;
    2          /product-category/category-slug-2;
    # other unlisted categories should redirect to /product-categories
    default    /product-categories;
}

map $arg_pid $url_pid {
    1          /product/product-slug-1;
    2          /product/product-slug-2;
    # other unlisted products defaults to /products
    default    /products;
}

server {
    listen ...
    server_name ...
    ...
    if ($url_p) { # if '$url_p' variable is not an empty string
        return 301 $url_p;
    }
    location / {
        ...
    }
    ...
}

Some map blocks can be shortened, for example, lets assume you have 3 static pages /career, /clients and "default" page /about, 5 categories and 45 products:

map $arg_id $url_id {
    ~^(career|clients)$        /$1;
    default                    /about;
}

map $arg_cat_id $url_cat_id {
    ~^([1-5])$                 /product-category/category-slug-$1;
    default                    /product-categories;
}

map $arg_pid $url_pid {
    ~^([1-9]|[1-3]\d|4[0-5])$  /product/product-slug-$1;
    default                    /products;
}

Update

OP states he can't use map directive since he doesn't have an access to full nginx config but only to server block contents. While previous solution is far more elegant (and should be more effective in terms of performance), it is possible to do the same using only if blocks:

if ($arg_p = contact) { return 301 /contact; }

if ($arg_p = static) { set $page static_$arg_id; }
if ($page = static_career) { return 301 /career; }
if ($page) { return 301 /about; } # anything that is not 'career' redirected to '/about'

if ($arg_p = catalog) { set $action $arg_action; }

if ($action = images) { set $page category_$arg_cat_id; }
if ($page = category_1) { return 301 /product-category/category-a; }
if ($page = category_2) { return 301 /product-category/category-b; }
# ... other categories
if ($action = images) { return 301 /product-categories; } # unlisted category specified

if ($action = viewimages) { set $page product_$arg_pid; }
if ($page = product_1) { return 301 /product/product-a; }
if ($page = product_2) { return 301 /product/product-b; }
# ... other products
if ($action = viewimages) { return 301 /products; } # unlisted product specified

# if you want to process any unlisted action in some special way
# if ($action) { ... } # 'action' query argument neither 'images' nor 'viewimages'

This fragment can be placed either in server or location context.

in flag
I got `nginx: [emerg] "map" directive is not allowed here`. Does map can not be used inside `server` block? I don't have access to modify nginx outside `server` block.
in flag
Found these question https://stackoverflow.com/questions/27358804/, so I think map is not an option for me. Is it possible to do just rewrite (regex for old query params) (new links)? I don't understand much about regex.
in flag
That's a nice trick to concat the query params and store them as a variable.
Score:1
in flag

I ended up with this solution.

location / {
  if ($arg_p = contact) { return 301 /contact; }
  if ($args ~ p=static&id=career) { return 301 /career; }
  if ($arg_p = static) { return 301 /about; }
  if ($args ~ p=catalog&action=images&cat_id=1) { return 301 /product-category/category-a; }
  if ($args ~ p=catalog&action=images&cat_id=2) { return 301 /product-category/category-b; }
  # and other cat_id
  if ($args ~ p=catalog&action=viewimages&pid=1&cat_id=1) { return 301 /product/product-a; }
  if ($args ~ p=catalog&action=viewimages&pid=2&cat_id=1) { return 301 /product/product-b; }
  # and other pid
  if ($arg_p = catalog) { return 301 /products; } #other p=catalog defaults to /products
  try_files $uri $uri/ /index.php$is_args$args;
}

It's working, but it can't handle the case when the query params order is not written below, e.g /?id=career&p=static (id and p is switched around)

Also cat_id in p=catalog&action=viewimages is not used, but when I remove the cat_id from the rules, p=catalog&action=viewimages&pid=10 always redirects to p=catalog&action=viewimages&pid=1, so I had to put the cat_id.

If someone had better idea to handle dynamic order for the query params, feel free to post as an answer. I'll mark it as accepted if it works.

EDIT: For dynamic order query params and much more cleaner if, see Ivan Shatsky's answer

Ivan Shatsky avatar
gr flag
See the update to my answer.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.