Nginx: Multiple cross-domain 301 redirects with different page addresses

301-redirecthttphttpsnginxredirect

I'm moving my old site over to a new domain, and with that new domain comes new naming conventions. I'm trying to figure out what would be the simplest way of accomplishing the following for roughly 8 different pages:

  1. http to https
  2. Different domain
  3. Redirect (1) old www and (2) old non-www addresses, plus (3) new non-www address to new www address

Here are two old pages from the old domain:

Portfolio:

http://dcturanoinc.com/?dct=portfolio_expediting
http://www.dcturanoinc.com/?dct=portfolio_expediting

Services:

http://dcturanoinc.com/?dct=services_expediting
http://www.dcturanoinc.com/?dct=services_expediting

Here are two new pages from the new domain:

Services:

https://dcturano.com/services/
https://www.dcturano.com/services/

Portfolio:

https://dcturano.com/portfolio/
https://www.dcturano.com/portfolio/

EDIT: This is my nginx.conf file as it currently stands.

server {
    listen 80;
    listen [::]:80;
    listen 443 default_server ssl;

    server_name dcturano.com www.dcturano.com;

    if ($scheme = http) {
        return 301 https://$server_name$request_uri;
    }

Best Answer

1. HTTP -> HTTPS

To automate detection of HTTP vs HTTPS, I would use 2 separate server blocks:

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    # Redirect to HTTPS
}

server {
    listen 443 default_server ssl;
    listen [::]:443 default_server ssl;

    # Serve content
}

Redirect will be done through the use of return 301 rather than using rewrite, which is discouraged when avoidable.

2. www.domain -> domain

Now to redirect www to non-www, I would use a map, avoiding to use if:

map $host $new_host {
    "~*^www\.(?<domain>.+)$"    $domain;
    default                     $host;
}

then use $new_host in combination with return 301

3. ?dct=portfolio_ -> ?dct=services_

Finally, to redirect old domains to new ones, it is the kind of arguments filtering explained on the link provided by @wurtel, although I would once again use a map instead of if.

map $arg_dct $new_arg_dct {
    "~*^portfolio_(?<dct>.+)$"    services_$dct;
    default                       $arg_dct;
}

4. All-in-one example

http {
    map $host $new_host {
        "~*^www\.(?<domain>.+)$"    $domain;
        default                     $host;
    }

    map $arg_dct $new_arg_dct {
        "~*^portfolio_(?<dct>.+)$"    services_$dct;
        default                       $arg_dct;
    }

    server {
        listen 80 default_server;
        listen [::]:80 default_server;

        return 301 https://$new_host/?dct=$new_arg_dct;
    }

    server {
        listen 443 default_server ssl;
        listen [::]:443 default_server ssl;

        if ($new_host != $host) {
            # Used $scheme to show you its existence
            return 301 $scheme://$new_host/?dct=$new_arg_dct;
        }

        if ($new_arg_dct != $arg_dct) {
            # Used $scheme to show you its existence
            return 301 $scheme://$new_host/?dct=$new_arg_dct;
        }
    }
}

As you noticed, the use of seperate servers is not really necessary here, as a simple return 301 https://[...] redirect would have been sufficient. However, I find it prettier if HTTP requests never made it to the right server. It would also help scale your configuration if you end up with services not (yet) supporting HTTPS.

As for the arguments, I took your provided examples to the letter. If you wish to have some other arguments you do not want to touch and you want to redirect in a generic fashion, you may need to chain several map (or use if if you know what you are doing) to rewrite the $args variable, ending up with a rewritten construction ready to be sent to the redirection URL.