Nginx HTTP-to-HTTPS AND domain.com-to-WWW Redirect Using AWS ELB for SSL Termination

Nginx HTTP-to-HTTPS AND domain.com-to-WWW Redirect Using AWS ELB for SSL Termination

Preface

2019 Update: I previously posted a short tutorial of how to achieve this simple redirect functionality within Nginx itself terminating SSL, but this is a more-robust solution that works with health checks from an AWS Elastic Load Balancer. Since this was posted a couple of years ago, it could have been a standard ELB, but I believe it was an Application Load Balancer (ALB), if I recall correctly. Either way, I’m pretty sure this method works for both. There was basically no documentation on this at the time, so I pretty much had to figure it out on my own.

The Amazonian breakdown

You are running Nginx as a webserver or reverse proxy, and you are terminating SSL on an Amazon Elastic Loadbalancer.

The loadbalancer is passing HTTP traffic from port 80 to HTTP port 80 on your EC2 instance(s).

The loadbalancer is decrypting HTTPS traffic from port 443 and also passing it on to HTTP port 80 on your EC2 instance(s).

You are looking for the following functionality:

Believe it or not, this is not as simple as it seems to setup, because of the way ELB health checks work. They are wanting direct access to the health check URI, but if they get directed, you will encounter some very hard to troubleshoot issues, especially if you are terminating SSL on the ELB itself while running an HTTP backend.

Below is a very simple sample virtual host config that will allow this setup to function as expected. In this example, I am listening on HTTP and HTTPS and redirecting all HTTP/non-WWW traffic to the HTTPS/WWW URL. There is also a global/restrictions.conf file that I have not included for simplicity’s sake, because it doesn’t directly relate to the main objective. In this case, I am using Nginx to reverse proxy traffic to an upstream php-fpm server, but you can easily add document root/index directives to serve the content.

/etc/nginx/sites-available/example.conf:
upstream php {
  server unix:/var/run/php5-fpm.sock;
}

server {
  listen 80;
  server_name domain.com www.domain.com;
  root /var/www;
  include global/restrictions.conf;
  
  location /healthcheck {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_next_upstream error;
    break;
  }

  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_next_upstream error;

    if ($http_x_forwarded_proto != "https") {
      return 301 https://www.domain.com;
    }

    try_files $uri $uri/ /index.php?$args;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;";
  }

  location ~ [^/]\.php(/|$) {
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    if (!-f $document_root$fastcgi_script_name) {
            return 404;
    }

    # This is a robust solution for path info security issue and works with "cgi.fix_pathinfo = 1" in /etc/php.ini:
    fastcgi_index index.php;
    fastcgi_intercept_errors on;
    fastcgi_pass php;
    include fastcgi.conf;
  }
}

Leave a Reply