AWS Static Website Hosting over HTTPS
A step-by-step guide to deploying your frontend resources on AWS with CloudFront, S3, Route 53 and Amazon Certificate Manager
Deploying a static website on AWS is a useful skill to have when your website’s architecture involves serving the frontend separately from the rest of the application. This is especially useful when launching projects such as simple personal websites that serve only a frontend, and can also be incredibly beneficial for complex full-stack applications which involve a backend that communicates with a static site through rest APIs.
In this guide, you will learn how to deploy your frontend resources by first storing the website’s build files in an AWS S3 bucket, and then serving the bucket contents using CloudFront. You will also configure AWS Route 53 in order to ensure that your desired domain points to the CloudFront endpoint, and finally, you will also be obtaining an SSL certificate to ensure that your website is served over HTTPS.
Services Used
- Amazon S3: To store website static build files
- Amazon CloudFront: CDN to serve website files
- Amazon Route 53: To buy and manage domains
- Amazon Certificate Manager: To obtain SSL certificates so that your website is secure by serving it over HTTPS
Costs
- Domain price: Varies depending on the domain (~USD$12 to ~USD$60 or more per year)
- S3 Storage: Free (AWS Free Tier for 1 year if this is your first time using AWS S3), else it goes for USD$0.23 per month.
Even though this guide uses a React application as a primary example, the deployment steps still work no matter which library or framework you are using. Let’s get started!
Step 1: Building your application
If you have a frontend application already ready to deploy to production, you must first build the website and obtain the production-ready resources. Here, I have a React project that builds when running the following command in the project’s directory:
npm run-script build
The project’s build files will be created in a build folder inside the project directory.
Step 2: Purchasing your domain name
Go to the AWS Route 53 console and under Register Domain, check if your desired domain is available. For example, I am deploying a website that generates random love letters (created in the spirit of Valentine’s Day), so the domain I am looking at is lovelettergenerator.com.
If the domain that you want is available, add it to your cart and click continue. If your domain name is not available you will have to choose another domain name. Enter your contact details and make sure that privacy protection is enabled if you are using a “.com” domain. This masks your personal details on the internet when someone looks up your details on a domain name data lookup website like ICANN. Check your details and complete the order.
The registration may take a while to be successful, but once its done, you should see that your domain name is now available as a hosted zone with two records already created (One that is NS type and one that is SOA type, we will not be touching these records).
Step 3: Creating a AWS S3 bucket to store your build files
Go to the AWS S3 console and create a new bucket in your region and choose your desired settings. As a best practice, name the bucket after the domain name in which you are using. In my case, I am deploying a project that generates random love letters on the domain lovelettergenerator.com, which will also be the name of my bucket.
I will also keep all the default settings, ensuring that public access to the bucket is blocked. This is important as you will only want your own CloudFront distribution associated with this website to be able to access the S3 bucket.
Once the bucket have been created, go to your project’s build folder, copy all the files inside the folder and upload it into the bucket. In the case of my project, the S3 bucket will look something like this:
Now go to the Properties tab in the bucket, scroll down to Static Website Hosting and enable it.
Be sure to specify your Index Document as the index.html file so that S3 can recognise the default object to serve your website from. For my project, it is a simple single page application without an error page. If your website has an error page, be sure to specify this under Error document. The S3 bucket is now set up, and we can proceed to obtaining an SSL certificate to serve the contents over HTTPS.
Step 4: Obtaining a SSL certificate
To request for a SSL certificate, go to AWS Certificate Manager (ACM) and first change the region to US East (N. Virginia). This is because CloudFront only recognises certificates stored on AWS Certificate Manager in the US East (N. Virginia) Region, at least on this day of writing. Once this is done, click Request for a Certificate.
Under Add domain names, put down your domain name which you registered for in Step 2. In my case, this will be lovelettergenerator.com, and under additional names I will input *.lovelettergenerator.com to ensure that this works even for users who type www.lovelettergenerator.com in their browser. I did not input anything for the rest of the settings but you should do so if it suits your needs.
Once this is done, we need to associate this certificate request to the domain we just bought in Step 2. This can be done by first downloading the DNS configuration file here and viewing it in Excel or similar softwares that can view CSV files:
Go back to the Route 53 console and click on the hosted zone for your registered domain in Step 2. We will need to create a CNAME (canonical name) record to associate your requested certificate to the domain. Under record type, select CNAME, and under record name and record value, copy and paste the record name and value columns from the CSV file respectively.
If you need to configure any of the other settings for the record, you may do so here, but in my case I will leave the rest of the settings as the defaults. Now after creating the record, go back to the AWS Certificate Manager and the certificate request should be approved shortly (~10mins).
Step 5: Setting up the CloudFront CDN
Go to the AWS CloudFront console and click Create Distribution. Under Origin Domain Name, select the S3 bucket you created in Step 3 and ensure that Restrict Bucket Access is enabled. When you do so, you should be presented with the option to configure the Origin Access Identity, where you can choose to create a new identity or use an existing identity. If you do not have an existing identity or do not want to use it for this project, click Create a New Identity, which is what I will be doing for my project.
Ensure that under Grant Read Permissions on Bucket, select Yes, Update Bucket Policy. This will update the S3 bucket policy to allow access to the CloudFront distribution, ensuring that the bucket contents are protected yet allowing CloudFront to serve the website files.
Notable settings which should be changed include the following.
In Default Cache Behaviour Settings:
- Viewer Protocol Policy: which should be Redirect HTTP to HTTPS since we already have an SSL certificate and there is no reason for our website to be served only over HTTP.
- Origin Request Policy: change this to Managed-CORS-S3Origin
And also in Distribution Settings:
- Alternate Domain Names (CNAMES): This should be changed to the domain names associated to your SSL certificate in Step 4. In my case this will be lovelettergenerator.com and *.lovelettergenerator.com. Ensure that you include each entry in a new line.
- SSL Certificate: Select Custom SSL Certificate and choose the certificate that you created in Step 4.
- Default Root Object: You have to specify the root object to be index.html so that CloudFront can recognise the file in your S3 bucket to serve as the root of the website (This is the same file you specified when enabling Static Website Hosting for the S3 bucket in Step 3). Not doing this step correctly will prevent your website from loading.
If you need to change any other settings due to your project’s needs, feel free to do so here. In my case, I left all the other settings as their defaults. Once you are done, create the distribution! This takes quite a while to deploy but once it is done, go to your S3 bucket which you listed as your CloudFront origin. Go to the Permissions tab and ensure that under Bucket Policy, a new policy have been automatically created by CloudFront if you have specified Yes, Update Bucket Policy earlier.
If you have chose to update it manually, you will have to paste the following snippet below as the bucket policy:
{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity originaccessidentity"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::yourbucketname/*"
}
]
}
Remember to update the fields in bold, where your CloudFront Origin Access Identity can be found in your distribution under the Origin and Origin Groups tab, and your bucket name is the name of your bucket from Step 3.
Once this is done, we can proceed to our final step!
Step 6: Linking CloudFront to your domain
In this final step, we will link CloudFront with your SSL enabled domain in Route 53. First, you will need to copy the CloudFront endpoint of the distribution you just created in Step 5. This can be found in your CloudFront main console, the area highlighted in red:
Now go to your domain’s hosted zone in the Route 53 console and create a new record. Change the record type to A — Routes traffic to an IPv4 address and some AWS resources (“A” stands for address) and this allows for DNS servers to have the IP address that corresponds to your CloudFront endpoint’s domain name. Click on the switch for Alias, and under Route traffic to select Alias to CloudFront distribution.
Finally, create a new record with record type CNAME — Routes traffic to another domain name and to some AWS resources, input www in record name and paste your CloudFront endpoint in Value. This links the domain to CloudFront even if a user inputs your domain with the www in front of the domain name.
After creating these two records your hosted zone should have 5 records, 2 which are created by default, 1 linking the domain to the SSL certificate, and 2 linking the domain to CloudFront.
Now that you’re done, you can visit your domain to view your website! (Over HTTPS of course)
The simple project that I deployed using this method can be seen here:
The next time you update your website’s source code, simply build it again, replace the S3 bucket contents with the new build files and you are good to go!
Troubleshooting
- 403 forbidden error: This means your CloudFront distribution does not have permission to access your bucket contents. Never make your bucket public in a desperate attempt to make this work. Instead, edit your CloudFront distribution’s origin under the distribution’s Origins and Origin Groups tab and selecting Create a New Identity and Yes, Update Bucket Policy. Update the changes and wait for CloudFront to redeploy.
- Website not appearing or “Access Denied” error code: You might have forgotten to set the CloudFront distribution’s default root object. If CloudFront is serving contents in a S3 bucket, the default root object has to be specified. Under the distribution’s General tab, ensure that the field for Default Root Object is set as index.html.
- Tried all of the above but the site still refuses to load: Patience! Sometimes, CloudFront takes a while to update any changes you make despite listing its status as “Deployed”. Do not panic, just let it sit for an hour or two, and then come back to check again (CloudFront can occasionally even take up to a day to update).
Conclusion
Hosting a static website on AWS is a common and important skill to have when deploying a project over a domain, especially in projects such as personal websites that might only contain a frontend. Serving your website over HTTPS is also vital not only because it is secure, but it also gives your users a better viewing experience and peace of mind.
I hope that this guide has been helpful and if you have any questions or found any errors, do feel free to reach out to me in the comments below or via my website!
Thank you for reading this article, and don’t forget to leave me a clap if the steps here managed to be of use to you!