AWS Website Hosting with HTTPS and Custom Domain
// 3 comments
Hosting websites like static sites or single page applications on Amazon S3 is a charm. However, it gets a bit complicated if you throw in HTTPS and your own domain name. There are literally a million article on the web on how to host a website on S3, especially since S3 was one of Amazon's earliest services, having been available since 2006. This article is not meant to be yet another step-by-step guide. Rather, it's going to be a summary of important notes and common questions I encountered, enriched with lots of links to the official docs and other sources. I will share a CloudFormation template at the end that can be used as a blueprint and be easily deployed.
Hosting
Amazon S3 supports static website hosting as a feature. You can enable this feature in your S3 bucket's properties, but only after the bucket has been created. I refer to this type of hosting as the classic approach. There is now a second, modern approach, to host websites with S3 that I will write about in a future post.
Naming
Let's start with naming. There are a few sources on the web that suggest that you have to name your bucket as your domain name.
To support requests from both the root domain and subdomain, you create two buckets: Domain bucket example.com and Subdomain bucket www.example.com
These bucket names must match your domain name exactly. In this example, the domain name is example.com. You host your content out of the root domain bucket (example.com).
Configuring a static website using a custom domain registered with Route 53
This requirement is a bit outdated and not well explained. If you're using Amazon Route 53 as your DNS service and you want to map a domain like example.com
to your S3 bucket, then the bucket name must match the domain name. If you're using a subdomain like www.example.com
, then the bucket name must match the subdomain.
However, Amazon S3 website endpoints do not support secure HTTPS connections. That means your domain will only be accessible via unsecure HTTP. If you want to use HTTPS (and you should!), you need to use another service like Amazon CloudFront that serves the static website hosted on Amazon S3 and it doesn't require the bucket to be named like the domain.
Permission and Public Access
It's important to note that your bucket and all its content must be publicly available, in order to host a public website. That means every single object in the bucket can be accessed on the web. So keep that in mind and put only content into the bucket that is related to your website and must be accessible on the web.
To make your bucket accessible on the web, you need to disable block all public access and add a bucket policy that allows GetObject
access.
On a side note, there are ways to keep the S3 bucket private (i.e. block public access setting) by using Amazon CloudFront origin access control (OAC). I will come back to this in a future post.
Access Control Lists
Access Control Lists (ACL) grant access to a specific resource in the bucket. However, you don't need to add any ACLs and they are disabled by default for new buckets.
A majority of modern use cases in Amazon S3 no longer require the use of ACLs. We recommend that you keep ACLs disabled, except in unusual circumstances where you need to control access for each object individually. With ACLs disabled, you can use policies to control access to all objects in your bucket, regardless of who uploaded the objects to your bucket. For more information, see Controlling ownership of objects and disabling ACLs for your bucket.
Managing access with ACLs
Endpoints
Amazon S3 provides a Website endpoint and a REST endpoint. The website endpoint follows one of these two formats:
http://<bucket>.s3-website-<region>.amazonaws.com/<key>
http://<bucket>.s3-website.<region>.amazonaws.com/<key>
Note the dot or dash between s3-website
and <region>
. The right format depends on your region, for example US East (N. Virginia) uses <bucket>.s3-website-us-east-1.amazonaws.com
while Europe (Frankfurt) uses <bucket>.s3-website.eu-central-1.amazonaws.com
. The full list is available on the S3 service endpoints.
Amazon S3 website endpoints do not support HTTPS or access points. If you want to use HTTPS, you can use Amazon CloudFront to serve a static website hosted on Amazon S3.
Key differences between a website endpoint and a REST API endpoint
The REST endpoint supports both a virtual-hosted-style and a path-style format:
https://<bucket>.s3.<region>.amazonaws.com/<key>
https://s3.<region>.amazonaws.com/<bucket>/<key>
Note the <bucket>
is in the host portion or in the path portion of the URL. However, the path-style format has been deprecated and shouldn't be used.
You may notice, that the REST endpoint supports HTTPS. However, it can't be used for static website hosting in a setup like this. The modern approach of hosting a website on Amazon S3 uses the REST endpoint instead of the website endpoint. More on that in my future post.
Index Document
The index document is the name of the file that Amazon S3 returns for a request to the root or any subfolder.
If you configure your website with index.html as the index document, either of the following URLs returns
index.html
.
http://<bucket>.s3-website<region>.amazonaws.com/
http://<bucket>.s3-website.<region>.amazonaws.com
Index document and folders
If your bucket has subfolders, the index document must have the same name on each level. However, which file is returned depends if the URL ends with a slash /
.
When a user specifies a URL that resembles a folder lookup, the presence or absence of a trailing slash determines the behavior of the website. For example, the following URL, with a trailing slash, returns the
photos/index.html
index document.
http://bucket-name.s3-website.Region.amazonaws.com/photos/
However, if you exclude the trailing slash from the preceding URL, Amazon S3 first looks for an object
photos
in the bucket. If the photos object is not found, it searches for an index document,photos/index.html
.
Index document and folders
Error Document
The error document is the file that is returned when an error occurs, but only for HTTP status code 404 Not Found.
The website endpoint responds with 404 Not Found for the following reasons:
- Amazon S3 determines that the URL of the website refers to an object key that does not exist.
- Amazon S3 infers that the request is for an index document that does not exist.
- A bucket specified in the URL does not exist.
- A bucket specified in the URL exists, but isn't configured as a website.
Amazon S3 HTTP response codes
HTTPS
Amazon CloudFront is a global content delivery network (CDN) and provides caching, security and other features. We are using it to serve HTTPS requests for the Amazon S3 bucket.
Origin
When you create a new distribution, you have to select your S3 bucket endpoint as the origin domain. Note that the dropdown for S3 buckets only shows the REST endpoints (<bucket>.s3.<region>.amazonaws.com
). However, if you select an entry, CloudFront will notice you that you should be using the S3 website endpoint <bucket>.s3-website-<region>.amazonaws.com
, because your bucket has static website hosting enabled.
There are various ways to use Amazon S3 as origin for CloudFront. We're using an Amazon S3 bucket that's configured as a website endpoint.
The Amazon S3 website endpoint only supports HTTP connections. That's the reason the protocol between CloudFront and the origin S3 will be HTTP only. This is different to the protocol between the viewer and CloudFront, which can be configured to be HTTPS.
HTTPS
The HTTPS support is a CloudFront behavior and has two options: redirect HTTP to HTTPS or HTTPS only. The first option will redirect with status code 301 Moved permanently while the latter will all HTTP requests via status code 403 Forbidden.
Custom Domain
To use our own custom domain, we have to add an alternate domain name (CNAME) and a custom SSL certificate. The certificate must be requested or imported into AWS Certificate Manager (ACM).
Certificate
When requesting a new certificate, it makes sense to add a domain name (such as example.com
) and another name with wildcard (such as *.example.com
) to the certificate, because an existing certificate can't be changed.
To use a certificate in AWS Certificate Manager (ACM) to require HTTPS between viewers and CloudFront, make sure you request (or import) the certificate in the US East (N. Virginia) Region (us-east-1).
AWS Region for AWS Certificate Manager
DNS
The DNS settings must be updated to route the traffic to the CloudFront domain name for your distribution. This step depends on your DNS provider. If you've registered your domains with Amazon Route 53, routing to CloudFront is straightforward. Create an Alias record that points to the corresponding CloudFront distribution's domain.
An alias record is a Route 53 extension to DNS. It's similar to a CNAME record, but you can create an alias record both for the root domain, such as example.com, and for subdomains, such as www.example.com. (You can create CNAME records only for subdomains.)
Choosing between alias and non-alias records
The DNS record type can be A
for IPv4 addresses and AAAA
for IPv6 addresses.
If IPv6 is enabled for the distribution, create two resource record sets to route traffic to your distribution, one with a value of A and one with a value of AAAA.
AWS CloudFormation: Route53 RecordSet Type
The alias is the target resource that the traffic is being routed to. When you work with the CloudFormation template directly, you have to specify a hosted zone id. This is always the same for CloudFront distributions.
Specify
Z2FDTNDATAQYW2
. This is always the hosted zone ID when you create an alias record that routes traffic to a CloudFront distribution.
AWS CloudFormation: Route53 RecordSet AliasTarget
CloudFormation Template
This template requires three inputs:
- ARN of an existing ACM certificate
- Name of the S3 that hosts the website
- Name of the Route53 domain
It can be deployed via CloudFormation console or the AWS CLI.
After the stack was created and deployed, it outputs three values:
-
HTTP URL of the S3 website endpoint
http://<bucket>.s3-website-<region>.amazonaws.com
-
HTTPS URL of the CloudFront endpoint
https://<distribution>.cloudfront.net
-
HTTPS of the Route53 domain name
https://<domain>