I’ve been setting up a separate website recently that needs only simple static content, and I thought I’d have an experiment with getting it HTTPS-enabled on AWS.
I had a couple of requirements:
- The site must be entirely HTTPS – any requests to a
http://...address should respond with a
301 Moved Permanentlyto the corresponding
- The site must be accessible from both the base subdomain (
example.com), and from the
www.example.com). To prevent oddities in search results, I want any requests to the base domain to redirect to the
Basically, I want this behaviour:
||< the static site content >|
I don’t much mind if there is more than one redirect to the final site.
Finally, I want this to be managed via CloudFormation.
I’m going to make use of the following AWS functionality:
- S3 can be used to host a static website, either directly, or as a source for CloudFront.
- S3 can also be used to serve up redirects.
- CloudFront can be used for SSL-termination to provide HTTPS endpoints.
- CloudFront can also be set up to redirect from
- ACM (Certificate Manager) can be used to provide an X.509 certificate suitable for HTTPS.
It took me a little while to realise that from AWS’s perspective, what I actually need to create are two distinct websites:
- One, on
www.example.com, will serve the static content.
- A second one, on
example.com, will simply serve a redirect.
Because of this, much of the CloudFormation script below is effectively duplicated.
I’m going to present the CloudFormation in this blog post as “literate code”, but if you prefer to see it all at once, I’ll link to a gist at the end of this article.
The overall structure looks like this:
This script takes a single input parameter,
RootDomainName, which is the root domain name, for example
First up is the
<region-map>. This is just a lookup table of AWS’s own endpoints and hosted zones for S3. I’m probably missing a few regions so if you’re hosting from a newer region you’ll need to update this table. (AWS documentation reference).
<subdomain-certificate> parts define ACM certificates that match the root domain (
example.com) and any sub-domains (
*.example.com). I need two certificates here because
*.example.com does not match
example.com. (AWS documentation reference).
IMPORTANT: Before AWS will issue a certificate for your domain, it needs to verify that you own the domain and are happy for the certificate to be issued. (Otherwise, you could ask AWS top issue certificates for any old domain, which would lead to their losing their status as a trusted certificate authority).
There are two ways AWS can verify you own a domain:
- It can send an email to an address
@your domain: reference
- You can set a specific CNAME entry in the DNS records for the domain: reference
Now the CNAME method is the preferred way, but I couldn’t see a way of getting all of that connected up in CloudFormation – the docs suggest that it isn’t directly possible.
Email sounds at first like it would be a lot more hassle – I haven’t yet set up email for my new domain – but as luck would have it, my domain registrar (NameCheap) enables email forwarding, so the AWS verification emails simply popped up in GMail.
What happens if you don’t verify you own the domain? The stack creation or update simply sits in the
*_IN_PROGRESS state indefinitely.
Onwards to buckets…
The bucket that will serve requests to the root (i.e. simply returning redirects) looks like the following:
It just points to the
www bucket, which looks like this:
You’ll need some other process to populate this bucket with the static files you want to serve of course – I don’t believe CloudFormation can handle that. And if you want your index and 404 files named differently, that’s up to you.
Next come the bucket policies, allowing public read on any new items (i.e. your content) that is added:
Next up are the definitions for the two CloudFront distributions. As far as I can tell, a CloudFront distribution serves one and only one set of content. If you want two sets of content (as we do here), then you need two distributions.
DefaultTTL for the
www distribution just for easier debugging. You might well want to change that value, or omit it and leave it at the default.
Finally, we come to DNS. We need a hosted zone (to hold the domain records), plus two actual domain records: one for the root, and one for
(That hosted zone ID is the one for CloudFront: reference )
These two records are AWS-specific, and I think they are extremely confusing. Why? The two mostly-used kinds of DNS records are:
Arecords, that point to IP addresses, and;
CNAMErecords, that point to other names.
The two records above are
ALIAS records – an AWS-specific bit of functionality. I think it would have been far clearer if in CloudFormation you did something like
Type: ALIAS, but sadly AWS decided that instead, you would define them as
Type: A, but indicate they are alias records using
AliasTarget. To my eyes it looks odd having an
A record that points to a name, but there we have it.
Just the Gist
If you’d like the code all in one place, you can find it here: GitHub Gist
So how much does this sort of static hosting cost?
The “big” item is hosted zones: these cost $0.50 per month each: Pricing docs.
(Note: this is a fixed cost and is not applied pro-rata. So for example, I got myself into a bit of a mess, decided to trash everything and re-create, and hence I have been billed for two hosted zones this month, even though I have only ever had one active).
If you plan on hosting multiple sites, this is likely to be your biggest cost.
So far, my other costs have been minimal. You’ll need to pay for the following:
- S3: I’m still in the free tier, so I can’t give an estimate until next year. Remember you pay for both storage and data transfer. Pricing docs.
- Route53: $0.40 per million queries and this does seem to be pro-rata. Pricing docs.
- CloudFront: Again, I’m still in the free tier, so I can’t estimate yet. Pricing docs.
- Taxes: Don’t forget these! I pay 20% VAT, but presumably this varies according to your location.