There are a bunch of blog posts and tutorials out there showing how to setup a static website on AWS. They all offer the same general solution with maybe some caveat. Route53 for DNS, CloudFront as a CDN to speed up content delivery, S3 to store the content, and ACM as a certificate provider. A good place to start off at when embarking on a new project is AWS own reference documentation. AWS static website project is a great example of this, and it will probably do a better job conveying the required information that I ever will. The only thing I think the example lacks is setting up it certificates for CloudFront. That was the source of most of my problems when setting up my static website, the very one you are on right now. There are many Stack Overflow questions and additional blog posts trying to solve these issues, but they are either just partially correct or outdated. That is why I am going to highlight some common issues rather than a tutorial, because a better one already exists.
**How to serve content for both www and non-www?**
If you are not to familiar with Route53 or any other you may assume that it
would be enough to setup a single A
record in Route53, point it to CloudFront
and consider that part done. Sadly it isn’t that simple, but the work required
to get it right isn’t enormous either as long as you know what to do. The
problem is that there are many Stack Overflow Questions, with old or outright wrong answers. On top of that we have sites that scrape Stack Overflow that perpetuates the same wrong answer.
As an example if you create an A
record for www.philiplaine.com
and point it to your CloudFront distribution.
Then browse to philiplaine.com
you will find that nothing will happen.
That is because the www record is considered to be a subdomain to the non-www record.
To solve this simply create another A record for philiplaine.com
and point it to www.philiplaine.com
.
Then edit your CloudFront distribution and add both www.philiplaine.com
and
philiplaine.com
to the Alternate Domain Names parameter. It may take some
time for the changes to take effect if the change is made on existing resources
but eventually the non-www domain will redirect to the www domain. Of course this
also works the other way around if you prefer to have a non-www domain as the
primary domain.
Single certificate for both domains
Now that you have two A
records you want to have a valid SSL certificate for
both domains. If you setup ACM with the domain name www.philiplaine.com
before creating the second record you will find that your browser will warn
you that the certificate can’t be trusted. That is because it is only
configured for a single domain. So you will have to create a new certificate
that has contains both the www and the non-www domain. The reason we are
creating a new certificate is that ACM does not allow editing of existing
certificates.
If you are setting up the certificate with Terraform the configuration will look a bit different as there are two parameters.
- domain_name - (Required) A domain name for which the certificate should be issued
- subject_alternative_names - (Optional) A list of domains that should be SANs in the issued certificate
I have noticed that there are some inconsistencies with how Terraform solves
this and what the AWS API expects. If you set the domain_name
to be the www
domain name, and the subject_alternative_names
to be a list containing both
the www and the non-www domain it will always want to recreate the resource
after creating it. This seems to be because Terraform does not expect that
input. So when it refreshes its state with the AWS API it will consider all
domain_names that is not the domain_name
to be in the
subject_alternative_names
causing your state to claim that there is a
difference in the amount of items in the list. I am currently exploring this
error further so that I can file a bug and fix the issue.
My S3 bucket routing rules are not working
If you have like me decided to use Hugo to build your website you
may face a problem when you start browsing around your new website that is now
hosted on AWS. The root page will show up fine but /posts/
or any other
detailed page will return a error. This is because S3 expects all routes to
reference a file and not a directory, so /posts/index.html
would work but the
links generated by Hugo will not work that way. Keep calm! There is a simple
solution to this problem that you probably already know about, S3 Redirection
Rules.
So you configure your S3 bucket to add the following redirection rule, that
will append index.html
to any route that does not have any key.
[{
"Redirect": {
"ReplaceKeyPrefixWith": "index.html"
},
"Condition": {
"KeyPrefixEquals": "/"
}
}]
Great! You apply the changes but the problem is still there. There is one
important detail that may have been overlooked when setting the CloudFront
origin. Redirection rules will only be applied when browsing to the S3 buckets
website url and not the api url. It is not always the first thing to check even
though they look different. The website url has the structure <bucket-name>.s3-website-<AWS-region>.amazonaws.com
while the api url has the structure http://<bucket-name>.<AWS-region>.amazonaws.com
.
Notice the additional s3-website
in the url. One of the requirements to use
the website url is that the bucket and its content needs to be public, it is
not possible to restrict access through this method with IAM. AWS has more
documentation
with additional details. I am personally a bit confused why this is, as it
allows access directly to the S3 bucket even if you want to serve the content
through CloudFront. In comparison if you were to setup a CDN that does not need
any of the static website features you could make the bucket private and setup
origin access to limit access to only a specific CloudFront distribution. If
you have a good explanation of why this is or a good solution to restricting
direct access, please let me know!
If you are interested in the Terraform and code that setup this website, with the solutions that I have described you can look at two of my repositories on Github.
For further reading I would recommend looking at Benjamin Rodarte’s post. I would probably not have face these problems if I followed it from the start, even if it does not include instructions for Route53.