wordpress nginx ssl redirect loop

Solution 1:

When Nginx processes a request, it first identifies the server block that will handle the request. This means that it will match the server_name and listen directives.

In your case, the single server block you have contains :

server {
    listen 80;
    return 301 $server_name$request_uri;
    listen 443 ssl spdy;
    root /var/www/wordpress;
    index index.php index.html index.htm;
    server_name www.example.com;
    ...

This will listen on both port 80 and 443. The fact that you have a return directive between the two does not matter as the return directive is not processed at this point.

Once the server block is matched, Nginx will move on to process other directives. In the case of the rewrite directives (e.g. return), they are processed in the order listed. In the case of location directives, they are processed based on specificity of match (the exact rules are listed here).

In order to implement your redirect, you should separate out your rewrite directive into a separate server block:

server {
    listen 80;
    server_name www.example.com;
    return 301 https://www.example.com$request_uri;
}

server {
    listen 443 ssl spdy;
    server_name www.example.com;
    root /var/www/wordpress;
    index index.php index.html index.htm;
    ...
}

Note that you need to specify that you are returning the HTTPS version and not the same (HTTP) version of the page. In some cases it may be preferable to hard-code the server name (e.g. if you want to redirect www and non-www traffic to the same HTTPS page).


Edit: To address your comment:

You want content served from https://www.example.com and you want the following redirects:

  • http://(www.)?example.com redirects to https://www.example.com
  • https://example.com redirects to https://www.example.com

To do this, you need 3 server blocks:

server { #Redirect non-https to https - match both www and non-www
    listen 80;
    server_name  www.example.com example.com;
    return 301 https://www.example.com$request_uri;
}

server { #Redirect https, non-www to https, www
    listen 443 ssl spdy;
    server_name example.com;
    ssl_certificate /etc/ssl/certs/www.example.com.certchain.crt;
    ssl_certificate_key /etc/ssl/private/www.example.com.key;
    return 301 https://www.example.com$request_uri;
}

server { #Main server block
    listen 443 ssl spdy;
    server_name www.example.com;
    root /var/www/wordpress;
    index index.php index.html index.htm;
    ssl_certificate /etc/ssl/certs/www.example.com.certchain.crt;
    ssl_certificate_key /etc/ssl/private/www.example.com.key;

    ...

}

A few important points of mention:

  1. Your SSL certificate should list both www.example.com and example.com as subject alternative names. Even though you are redirecting away from example.com, an SSL connection is still established before the redirect. Without a valid certificate for example.com the user will get an invalid certificate warning and the redirect will not occur. (This also implies that you must include the certificate in the https://example.com block)

  2. Since having a redirect requires multiple SSL server blocks, it is preferable to move some of your SSL configuration outside the server block (into the http block). These include: ssl_session_timeout, ssl_session_cache, ssl_protocols, ssl_ciphers, ssl_prefer_server_ciphers, ssl_stapling, ssl_stapling_verify, ssl_trusted_certificate, ssl_dhparam, and your HSTS header. (As an aside, I highly recommend looking over Mozilla's Server Side TLS page)