On AWS, if you use a load balancer (ELB or ALB) you may wonder how to properly add security headers. In our situation we already redirect all HTTP requests to HTTPS. However, our security organization wanted to explicitly see HSTS headers. In this article I will explain how to do this when using AWS load balancers in your ecosystem.
What is HSTS?
HSTS stands for HTTP Strict-Transport-Security. It’s a type of header that can be added to instruct browsers that your website should only be accessed over HTTPS going forward. Personally, I thought it might be possible to add these headers at the load balancer level. While AWS did add some new options for ALBs (like perform action based on cookie presence), setting a header was not one of them.
I think this StackOverflow answer explains the reasoning well. Essentially, the header needs to be set at the source: your web server. You also shouldn’t set this header for HTTP requests (as it won’t do anything), so you will need to ensure that your ALB is communicating with your EC2 instance via HTTPS (port 443).
Configuring HSTS
HSTS is a header that can be configured in your web server configuration files. In our case, we’ll be setting it up with our web server, Nginx. You’ll need to use the “add_header” directive in the ssl/443 block of the web server config (for me, /etc/nginx/sites-available/sitename.conf — though depending on your setup, the directory structure may be different).
## The nginx config server { listen 80; server_name localhost; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name localhost; # insert HSTS headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Once you’ve added your header, close the file and do nginx -t
to test the config for any errors. If all checks out, do service nginx restart
or systemctl nginx restart
to apply the change.
About HSTS options
You’ll see in the section above, the Strict-Transport-Security header has a few options or flags appended. Let’s dive into what they mean:
- max-age=31536000: This directive tells the browser how long to hold onto this setting, in seconds. 31536000 seconds is 365 days, or one year. That means your browser will remember to only load the website over HTTPS for a year from the first time you accessed it. The minimum max-age is 18 weeks (10886400 seconds), though .
- includeSubDomains: This setting tells the browser to remember the settings for both the root domain (e.g. https://yoursite.com) and for subdomains (e.g. https://www.yoursite.com).
- preload: This setting is used when your website is registered as “preload” with Chrome at https://hstspreload.org/. This tells all Chrome users to only access your website over HTTPS.
Verify HSTS
If HSTS is working properly, you’ll see it when curling for headers. Here’s what it should look like:
$ curl -ILk https://yourdomain.com HTTP/2 200 date: Thu, 16 Jan 2020 19:25:15 GMT content-type: text/html; charset=UTF-8 server: nginx x-frame-options: Deny last-modified: Thu, 16 Jan 2020 19:25:15 GMT cache-control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 strict-transport-security: max-age=31536000; includeSubDomains; preload
If you prefer, you can also use the Inspect Element tools in your browser when you visit your domain. Open the Inspect Element console by right-clicking in your browser window, then click Network. Navigate to your website, and click the entry for your domain in the dropdown when it loads. You should see headers, including one for “strict-transport-security” in the list.
If you’re not seeing the header, check these things:
- Did you restart nginx? If yes, did it show an error? If it did show an error, check the config file for any obvious syntax errors.
- Did you make the change on the right server? I made this mistake at first too, which took me down a rabbit hole with no answers trying to find why AWS was stripping my HSTS headers. They weren’t, I was just making a very preventable mistake.
- Is your ALB/ELB sending traffic to your EC2 instance over HTTPS/port 443? HSTS headers can’t be sent over HTTP/port 80, so you need to make sure that the load balancer is communicating over HTTPS.
Have you had issues enabling HSTS headers on AWS? Have experiences you want to share? Feel free to leave a comment, or contact me.
Leave a Reply