Common S3 Static Website Problems

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.