Tutorial: Adding https to a custom domain on Elastic Beanstalk

The elusive green padlock has thwarted many a budding AWS Architect.

As much as I love AWS, sometimes it feels like it’s the world’s best toy store but the aisles are covered with landmines.

With many procedures, they’re really easy once you know the exact steps and the gotchas along the way, but getting to that point can be a miserable experience of wading through incomplete documentation and tangential Stack Overflow advice.

Elastic Beanstalk is one such example. It’s a fantastic tool once you get it configured but I’ve come close to breaking a few keyboards in the process. In between where the documentation says “type eb deploy” and “you’re done”, there’s a good weekend’s worth of pain deciphering cryptic error messages and wondering why the environments are not the same.

In this tutorial, I have an Elastic Beanstalk application with a custom domain name and I want to deploy https. Let’s do this thing.

EB = Elastic Beanstalk
ELB = Elastic Load Balancer
OMG = Sometimes I hate my choice of career!!

Foolish assumptions

  1. In your EB setup, you are using a load balancer and not running a “no ELB/single-instance” config. If you are running a single-instance EB, there are ways to get SSL running but that’s not what I’m covering here.
  2. You are using Route 53.
  3. You have a custom domain already set up (e.g. http:// somewebsite.com).
  4. The EB application is currently running and you have the endpoint available (e.g. http:// env.12345678.region.elasticbeanstalk.com/

The tutorial

  1. Go to Certificate Manager, click “Request a certificate” and follow the verification process. For this example, our app will be myapplication.mydomain.com.
  2. Log into the AWS console, go to Route 53, click the hosted zone for this domain, click “Create Record Set”. On the right, you will see:
  3. This is the first gotcha. You have to select the A-IPv4 address (not CNAME, as in the documentation, or nothing will appear in the Alias dropdown). Select “Yes” to alias. Under targets, select your EB target not your ELB target:
If you select the ELB target, it will work until the environment changes and the ELB is reconfigured. I learned this the hard way.

3. Go to Elastic Beanstalk in the console, select your environment and click “Configuration” on the left. Find Network Tier and click the “Load Balancing” cog icon. Change Secure listener port to 443 and find the SSL certificate from the drop-down (you created this in step 1):

4. Allow the environment to reconfigure itself and https://myapplication.mydomain.com will now work. You may have to clear your dns on your local machine or at least wait a while if it doesn’t work straight away.

Pitfalls, gotchas, and ‘why was this so hard’?

The configuration in Elastic Beanstalk is relatively new — previously, you had to edit files in .ebextensions, configure load balancers and set security groups manually. Consequently, the official documentation is inconsistent and advice in tech forums is out of date.

I made some critical errors that cost me a weekend. Specifically:

  • If the Route 53 configuration is wrong, it will all appear to work with http (good) but https always loads without a certificate (bad).
  • If the certificate is applied to the load balancer directly, it will work (good) until the environment changes and the original configuration is reapplied (bad).
  • The Certificate must be in the same region as the EB (obviously) unless you’re using CloudFront (where certs must always be in us-east-1, not so obviously). It’s easy to get cross-region out-of-syncs if you don’t pay close attention.
  • One ELB serves one EB using one SSL certificate. Let’s not get crazy here.

Like many AWS services, it’s the minor, tiny details that can derail you. But once you’ve found the path through the maze, it really looks extremely simple. Joyously, this setup will auto-renew itself forever and ever so automation FTW.

So this was the easy way of creating your very own custom domain with https on an Elastic Beanstalk application using Certificate Manager. I honestly hope it saves somebody a weekend.

Bonus Tip: Redirecting http to https at the Apache level.

One immediate annoyance is that if your visitors goes to the http version of the site, they are not redirected to https. And if you use a framework like Flask, which I know and love well, functions such as url_for can often direct between the two somewhat arbitrarily.

There are a couple of ways to deal with this, so I’ll start with the bad ones together with their levels of terribleness:

  • Use CloudFront in front of ElasticBeanstalk — it works but good God, no, what a terrible idea. Pour holy water on this one.
  • Use SSH to get into the box and play with the Apache settings yourself. This misses the whole idea of using the Beanstalk in the first place and really shouldn’t be entertained. It’s scary to think we even thought of this.
  • Do it at the application level. This would work but it’s a maintenance nightmare and really putting the logic in the wrong place. It’s like tinting windows instead of wearing sunglasses.
  • Use an undocumented ebextensions script that does it for you. This is the winner! Hurray.

And it’s pretty simple — and I don’t know who figured this out originally because it wasn’t me. Just create a file in the .ebextensions directory called apache.config (the name doesn’t matter) and paste the following:

files:
"/etc/httpd/conf.d/ssl_rewrite.conf":
mode: "000644"
owner: root
group: root
content: |
RewriteEngine On
<If "-n '%{HTTP:X-Forwarded-Proto}' && %{HTTP:X-Forwarded-Proto} != 'https'">
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
</If>
Note! YAML uses spaces, not tabs, so make sure you use spaces in the snippet above or you will get a cryptic error message.

On the next eb deploy, everything will now always be https. Phew.