Nginx – wordpress nginx ssl redirect loop

nginxsslWordpress

So I'm setting up an nginx server and installed wordpress and SSL.

The site is working perfectly on both http and https but when I try to redirect http to https via nginx's server block, both http and https results in a endless redirect loop.

Here's my server block

    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;
    ssl_session_cache shared:SSL:20m;
    ssl_session_timeout 10m;
    spdy_headers_comp 6;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_certificate /etc/ssl/certs/www.example.com.certchain.crt;
    ssl_certificate_key /etc/ssl/private/www.example.com.key;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
    add_header        Alternate-Protocol  443:npn-spdy/2;
    proxy_set_header X-Forwarded-Proto https;

    access_log   /var/log/nginx/example.com.access.log;
    error_log    /var/log/nginx/example.com.error.log;

    error_page 404 /404.html;

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
            root /usr/share/nginx/html;
    }


location / {
            proxy_set_header        X-Forwarded-Proto $scheme;
            # try_files $uri $uri/ =404;
            try_files $uri $uri/ /index.php?q=$uri&$args;
    if ($http_referer ~* (buttons-for-website.com)) { return 444; }
    if ($http_referer ~* (semalt.com)) { return 444; }
    }


    location ~ \.(hh|php)$ {
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-Ssl on;
        fastcgi_keep_conn on;
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include        fastcgi_params;
        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache $skip_cache;
    fastcgi_cache microcache;
    fastcgi_cache_valid 200 60m;

 }

  location ~ \.php$ {

  location @fallback {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache microcache; fastcgi_cache_valid 200 60m;

 }

 # Cache Static Files For As Long As Possible
location ~*
 \.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|$
{
  access_log off;
  log_not_found off;
  expires max;
   }
# Security Settings For Better Privacy Deny Hidden Files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Return 403 Forbidden For readme.(txt|html) or license.(txt|html)
if ($request_uri ~* "^.+(readme|license)\.(txt|html)$") {
 return 403;
}
# Disallow PHP In Upload Folder
location /wp-content/uploads/ {
location ~ \.php$ {
deny all;
}
}
}

I'd really appreciate anyone's help. I commented out that "return 301" in 3rd line and google indexed both http and https version of the same page and deindexed most of my pages and dropped rankings for several keywords.

Thanks a bunch in advance!

Best Answer

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)