Setting up HTTPS redirects behind an AWS load balancer
The easiest way I’ve found to set up https is by creating a free https certificate with AWS Certificate Manager and adding it a load balancer. You then put a webserver behind the load balancer listening on http port 80 (because the load balancer terminates the https connection).
TL;DR? Skip to the final solution.
Problem #1
Your site works great at https://mycoolsite.com, however, if a user goes to http://mycoolsite.com, they receive a 404, because the load balancer rejects connections to port 80.
Solution #1
Make the load balancer listen on http port 80 as well, and proxy traffic through to the webserver.
Problem #2
Now users can connect to http://mycoolsite.com, but they should not be able to view your site over http, you want them using https. A quick google around and you might find a solution like this:
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
But you will soon find that doesn’t work, because all traffic connects to your server on port 80 and is then redirected to https, only to come back on port 80 again, because of the load balancer. You’ve created an infinite loop!
Solution #2
A little more googling you might come across a solution like this:
server {
listen 80;
server_name _;
if ($http_x_forwarded_proto != "https") {
return 301 https://$host$request_uri;
}
}
The load balancer helps you out by setting extra headers on the request that
it’s proxying to you. The X-FORWARDED-PROTO
header is the one which tells
you the initial protocol that the user connected with. You can check if it’s
been set to https
and if it hasn’t redirect to https, and depending on your
AWS setup, this might work.
Problem #3
The load balancer sends periodic health checks to your instance to check that
it’s alive and well. It doesn’t set the X-FORWARDED-PROTO
so nginx will
return a 301 and the load balancer thinks it’s unheathly. However, if all your
instances are unhealthy, then the load balancer will just send traffic to one
of them anyway, meaning you might not even know about the issue. I ran a
production servers for 2 months and never knew that the load balancer
considered them unhealthy. This problem was even listed in the
AWS documentation until
October 2017
so you can understand the confusion some people were having.
Solution #3
It wasn’t until I read this post
which showed apache config which negated the if check. So it only redirected
if the X-FORWARDED-PROTO
header was set to http
, meaning that someone
connected to the load balancer with http
and not https
. So I tweaked my
nginx config:
server {
listen 80;
server_name _;
if ($http_x_forwarded_proto = "http") {
return 301 https://$host$request_uri;
}
}
And now everything works as expected!