Serve a secure, static website with AWS

In this tutorial we’ll configure the necessary resources to securely serve a static website with a Top Level Domain (TLD) using AWS. There are a few twists I’ll walk you through along the way.

Aaron White is an AWS Certified Solutions Architect — Associate

You will incur costs following this tutorial — please reference the AWS pricing guide to estimate charges.

I’ll use several AWS services including Simple Storage Service (S3), Route 53, Certificate Manager, and CloudFront. These services enable us to host and securely serve a static website with a TLD. I’ll use the domain name www.placesiwanttovisit.com, which I previously registered with Route 53.

Registering a domain name with Route 53 is quite simple and relatively inexpensive. Transferring a domain name from another Registrar (Dynadot, GoDaddy, etc), to Route 53 can be slightly more complicated and may require additional time to complete the domain transfer process. Both of those processes fall outside the scope of this tutorial. You don’t absolutely need to have a domain name to follow this tutorial, however, it is recommended as you won’t be able to fully complete the process.

You may register a new TLD using Route 53 for as little as $8 (.me.uk). A ‘.com’ is $12 and there are lots of other reasonably priced TLD extensions available as well.

Also be aware there is a $0.50/mth charge for using a Route 53 Hosted Zone for a domain. By following this tutorial you agree that I am not responsible for any fees you may incur.

If you already have a domain registered with Route 53 and are following this tutorial, consider setting the domain’s Time To Live (TTL) to 60 seconds, temporarily. Some Internet Service Providers (ISP’s) may not honor your TTL’s, but if you’re making changes to your domain, it’s a good practice to lower these thresholds, temporarily (displayed below).

We’ll start with S3. S3 is AWS’s serverless object storage service. What?!? Serverless essentially means that AWS abstracts the server management responsibility away from customers. It’s not that there aren’t servers, it’s that AWS alleviates that responsibility from its customers — which is terrific and why I elect to regularly use S3 to serve static websites.

Route 53 is AWS’s domain registrar and DNS service. Certificate Manager is the AWS service for creating and managing SSL certificates and CloudFront is AWS’s Content Delivery Network (CDN). Marrying these services together enables you to continually serve a performant static website over SSL.

To begin, login to your AWS Console and navigate to S3. You may find S3 by entering ‘S3’ in the search box or by selecting ‘S3’ under ‘Storage’ — both are displayed in the screen shot below.

Select S3 to host a static website

S3 is a global service, as indicated in the navigation header (displayed below). Regardless, a Bucket is created in a specific Region — a Region is a grouping of two or more geographically distinct data centers. Contents in your S3 Buckets are replicated to at least three Availability Zones, this remains true in Regions where fewer than three Availability Zones are publicly available.

Create an S3 Bucket by clicking the ‘Create bucket’ button.

A modal will pop up asking for your Bucket name and Region. Bucket names are globally unique and have restrictions, so you probably won’t be able create a Bucket named ‘mytestbucket’ or ‘bill’, etc. I’m going to host a website for the domain name www.placesiwanttovisit.com in this tutorial, so I’ll call my Bucket ‘placesiwanttovisit’. If ‘placesiwanttovisit’ was already taken, I’d try ‘placesiwanttovisit2020’ or ‘placesiwanttovisit-CA34535’ (randomization of characters and numbers) — your Bucket name is primarily a reference for you and may not be changed after creation.

After you’ve entered a unique Bucket name, select the Region where you want this Bucket to be created. Most customers select a Region based on proximity closest to users or based on pricing (S3 pricing varies by Region). Click the ‘Next’ button to continue to ‘Configure options’, then click ‘Next’ again as we don’t need to specify additional configurations at this stage.

Under ‘Set permissions’, de-select the ‘Block all public access’ checkbox (displayed below) in the middle of the screen as we WANT to serve a public static website from this Bucket. Click the ‘Next’ button followed by clicking the ‘Create bucket’ button.

Congratulations, you’ve successfully created an S3 Bucket. Next, click on your Bucket’s name to configure this Bucket so it can serve a static website.

You’ll be presented with a screen similar to the below screen shot. Click the ‘Properties’ tab.

Next, click on the tile labeled ‘Static website hosting’.

Clicking the ‘Static website hosting’ tile will expand its contents as pictured below.

Select the first option, ‘Use this bucket to host a website’. Enter ‘index.html’ into the ‘Index document’ and ‘Error document’ inputs as displayed below.

Complete this step by clicking the ‘Save’ button.

Upon completion, a check mark will illuminate to the left of the ‘Bucket hosting’ label, as displayed below.

We have now configured a Bucket to host a static website, however, the Bucket doesn’t have the appropriate permissions for public access — this is one of the twists mentioned earlier in this tutorial.

Click on the ‘Permissions’ tab as displayed in the below screen shot, next select the button labeled ‘Bucket Policy’. (second row of tabs)

What is a Bucket Policy? S3 is an object storage service and AWS uses the principle of least privilege. This means everything’s securely locked down unless an authorized account explicitly grants permission to allow others access to specified resources. In our use case ‘public’ is who we want to grant ‘read’ permissions to — so ‘public’ can view the website.

To serve content to ‘public’, we need to explicitly apply a policy to the Bucket which permits access to the contents of the Bucket. AWS policies are specified in JSON format. Below is the policy you’ll need to apply to enable public access. Copy & paste the text below into your Bucket policy’s editor.

Note: change YOUR_BUCKET_NAME to the name of your Bucket.

{
"Version": "2012-10-17",
"Id": "Policy1546483269008",
"Statement": [
{
"Sid": "Stmt1546483267692",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
}
]
}

Since my Bucket’s name is ‘placesiwanttovisit’ I’ll paste ‘placesiwanttovisit’ into the Bucket policy editor as displayed below. Note, do not remove the ‘/*’ at the end of the line as that ensures ‘public’ has access to all resources in the Bucket.

Click the ‘Save’ button which appears above the Bucket policy editor at the right. If you receive an error, ensure you correctly entered your Bucket’s name. Errors may result from misspellings or the removal of punctuation.

Upon successfully applying this policy on your Bucket, an alert will notify you that your Bucket now has a ‘Public’ policy applied to it.

CONTENTS OF BUCKET ARE PUBLICLY AVAILABLE

Now, you’re ready to upload a file to the Bucket. Click the ‘Overview’ tab and you’ll be taken to a screen where you can upload a file. Click the ‘Upload’ button.

I’ll upload an ‘index.html’ file containing the contents below. For the simplicity of this tutorial, my ‘index.html’ file contains the contents below.

<html>
Places I Want To Visit
</html>

After clicking the ‘Upload’ button you’ll be presented with the following screen.

Click the ‘Add files’ button, navigate and select the ‘index.html’ file from your computer.

Next click the ‘Upload’ button at the bottom left of your screen. You should now see that your file has been uploaded, similar to the below screen.

View the meta data of your file by clicking on the Name of your file.

At the bottom of that screen you’ll see a link to the Object URL. Clicking on that link should render your static HTML page. If you copy & pasted the code I provided verbatim, you’ll see something similar to the below.

The domain for this bucket is:
placesiwanttovisit.s3-us-west-2.amazonaws.com
…this will be referenced in CloudFront later.

Obviously we want to serve content from our domain name, not ‘placesiwanttovisit.s3-us-west-2.amazonaws.com’ (your URL will be different). We’re unable to point https://www.placesiwanttovisit.com to this S3 Bucket because we don’t have an SSL Certificate.

If you haven’t registered or transferred a domain name into Route 53 you won’t be able to follow the rest of this tutorial.

Presuming you have a domain name registered with Route 53, along with an active Hosted Zone (displayed below), our next step is to request an SSL Certificate.

Navigate to ‘Certificate Manager’. You may do this by using the ‘Services’ label on the menu at the top of the screen. Next enter ‘Certificate’ in the search box as pictured below, then click on ‘Certificate Manager’.

Alternatively, you may navigate back to the AWS Console Management dashboard and select ‘Certificate Manager’ under ‘Security, Identity, & Compliance’.

We’ve reached another twist. Our SSL Certificate has to be created in ‘US East (N. Virginia) us-east-1’ for usage with a CloudFront distribution. If your Region isn’t already set to ‘N. Virginia’, use the top right portion in your navigation to change your Region. Select your Region (displayed below), in my case I’m in the ‘Oregon’ Region — select ‘US East (N. Virginia) us-east-1’.

Before continuing, make sure ‘N. Virginia’ is displayed in the right portion of the navigation menu as pictured below. Under ‘Provision certificates’ click the ‘Get started’ button (left ‘Get started’ button near bottom).

On the next screen ensure ‘Request a public certificate’ is the selected option under ‘Request a certificate’, then click the ‘Request a certificate’ button at the very right bottom part of your screen. Depending on your screen size, you may have to scroll either vertically, horizontally, or both to view this button (my screen shot below has been resized to fit appropriately).

Next we’ll create a wildcard SSL Certificate so we may use it on multiple sub-domains (such as ‘www.placesiwanttovisit.com’, ‘api.placesiwanttovisit.com’, ‘images.placesiwanttovisit.com’, etc). In this tutorial we’re only going to use this SSL Certificate for ‘www.placesiwanttovisit.com’, however, creating the wildcard SSL Certificate provides options for the future.

To create a wildcard SSL Certificate enter ‘*.YOUR_DOMAIN_NAME.ext’ in the domain name input box — since my domain is ‘placesiwanttovisit.com’ I entered ‘*.placesiwanttovisit.com’ in the input as pictured below. Your input WILL NOT be ‘*.placesiwanttovisit.com’.

Click the ‘Next’ button and select the ‘Email validation’ option, then click the ‘Next’ button again.

Why ‘Email validation’? In my experience, email validation seems be quicker than DNS validation even when a domain is registered and has a hosted zone in Route 53. The email validation request will be sent to the registered domain owner for each domain name in the certificate request.

On the next screen you may skip adding tags and click the ‘Review’ button. On the last screen click the ‘Confirm and request’ button.

Your SSL Certificate will be issued with a ‘Status’ of ‘Pending validation’ until you complete the validation process.

Hopefully within minutes you’ll receive an email from ‘Amazon Certificates’ to validate your SSL Certificate request — looking something like the below screen shot.

I would advise you NOT to delete this email until the certificate has been Issued — I’ve had to re-open the email (from my Trash folder) and attempt the approval process again (sure, it could be timing thing — but it seems to occur semi-regularly enough to make me think the ‘I Approve’ button doesn’t always work)

Clicking the approval link will send you to a basic AWS page with an ‘I Approve’ button at the bottom portion of the screen.

Click the ‘I Approve’ button then navigate to Certificate Manager in the AWS Management Console. The ‘Status’ should now display as ‘Issued’.

If there aren’t any certificates being displayed, ensure you’re in the Region ‘N. Virginia’.

As mentioned above, I’ve experienced this process taking several minutes, so you may have to exercise some patience. Give it a few minutes and try refreshing the page in your browser a few times. If the ‘Status’ doesn’t update after 5–10 minutes, re-open the approval email and click the ‘I Approve’ button again. You may have to exercise some more patience as it can take a little time for the ‘Status’ to update. The good news is that these SSL Certificates are FREE and automatically renew for us — so it’s worth the wait!

Congratulations! You’ve successfully created an SSL Certificate for your domain — we’ve also encountered another twist.

AWS does NOT provide a methodology to attach an SSL Certificate to an S3 bucket. To serve a website using this SSL Certificate with S3 we’ll need to create a CloudFront distribution. A CloudFront distribution puts your website’s content closer to end users by using AWS’s vast network of Edge servers —this results in lower latency for visitors accessing the website.

After the Certificate has been ‘Issued’ navigate to CloudFront using the Services navigation option previously used.

Upon entering the CloudFront dashboard click the ‘Create Distribution’ button.

On the next screen click the ‘Get Started’ button under Web (first ‘Get Started’ button).

This next screen can be a bit overwhelming — please review my entries CAREFULLY!!! A CloudFront distribution can take 30 minutes or more to create and missing ANY of these steps will require a re-deployment of your CloudFront distribution, again, which can take another 30 minutes or more.

The first input is labeled ‘Origin Domain Name’. For this input select the domain for the S3 Bucket noted earlier in this tutorial. I will select the domain: ‘placesiwanttovisit.s3.amazonaws.com’. Your S3 domain will be different.

Selecting options in some of these inputs can be challenging at times. Focusing your cursor inside the inputs then using keyboard navigation (up & down arrows) can be an effective methodology for selecting your desired values.

Under ‘Default Cache Behavior Settings’ select the option labeled ‘Redirect HTTP to HTTPS’ to ensure all traffic is being served securely.

Next, scroll down to the ‘Distribution Settings’ section. I’ll select ‘Use Only U.S., Canada and Europe’ for this tutorial as its the least expensive price class.

Enter your domain name into the ‘Alternate Domain Names (CNAMEs)’ input. I entered ‘www.placesiwanttovisit.com’ as that’s the sub-domain I intend to use for my website. Next, select the option labeled ‘Custom SSL Certificate (example.com):’ under the ‘SSL Certificate’ option.

If ‘Custom SSL Certificate (example.com):’ is not an available option, ensure your SSL Certificate was created in the ‘N. Virginia’ Region. If it wasn’t, you’ll have to create an SSL Certificate in ‘N. Virginia’ to proceed.

Select your wildcard certificate from the ‘Custom SSL Certificate (example.com):’ option — you may need to use keyboard up & down arrows to select this entry.

Scrolling down further you need to ensure ‘index.html’ is entered inside the ‘Default Root Object’ input value, as pictured below.

We’ve entered all the necessary values on this screen. At the bottom right of your screen click the ‘Create Distribution’ button.

Your CloudFront distribution is being created — and that’s going to take a while.

Begin Sidebar for Modern JavaScript Applications

If you’re running a modern JavaScript application you may experience a challenge — another twist.

By default, CloudFront distributions aren’t configured to direct errors to the ‘Error document’ we specified in the S3 Bucket. This can be a problem for customers running a modern JavaScript application with routing.

Example of dynamic routing in React:

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import App from './App'
import ErrorHandler from './ErrorHandler'
ReactDOM.render(
<BrowserRouter>
<Switch>
<Route path="/" exact component={App} />
<Route path="/place/:id" component={Place} />
<Route component={ErrorHandler} />
</Switch>
</BrowserRouter>,
document.getElementById('root'))

In the next steps we’ll configure CloudFront to handle requests so when visitors request a resource, such as https://www.placesiwanttovisit.com/place/1234, they’ll be routed to the appropriate resource. Likewise, if they request https://www.placesiwanttovisit.com/test.html, a ‘Not Found’ resource will be displayed. It’s not important that you understand the above React code, but it’s helpful to understand how to address this before encountering it.

In CloudFront Distributions, click on the ID of your newly created distribution.

You’ll see a screen similar to what’s displayed below.

Click the tab labeled ‘Error Pages’ as displayed below.

Click the button labeled ‘Create Custom Error Response’.

We’ll create two error handlers. One for 404 errors and one for 403 errors.

Select ‘404: Not Found’ from the ‘HTTP Error Code’ option, then select the value ‘Yes’ for ‘Customize Error Response’ as displayed below. Next enter ‘/index.html’ (must have the front slash prefix) as the value for the ‘Response Page Path’ and then select ‘200: OK’ for the ‘HTTP Response Code’ (as displayed below), then click the ‘Create’ button.

You’ll be redirected to the CloudFront distribution screen. Click the ‘Create Custom Error Response’ button again.

This time select the ‘403: Forbidden’ value for ‘HTTP Error Code’ and configure the settings the same as the previous instructions (displayed below). Next click the ‘Create’ button.

You should now have two error settings for your CloudFront distribution as pictured below.

End Modern JavaScript Applications Sidebar

We’re now ready to point the ‘www’ version of the domain to this CloudFront distribution.

Navigate to Route 53 (you should know how to search under the ‘Service’ navigation label by now) and click the ‘Hosted zones’ link.

Next, click on the domain name you’re using for this tutorial, I’m using placesiwanttovisit.com so I’ll click on that domain.

You’ll be presented with a screen, similar to the below image. Note that I previously set my TTL’s to 60 seconds to expedite DNS changes for this domain.

Click on the ‘Create Record Set’ button.

At the right pane of your screen (pictured below), enter ‘www’ (no dots or spaces) in the input for ‘Name’, which displays in front of your domain name. Select the value ‘Yes’ for the ‘Alias’ option, selecting this option will display the ‘Alias Target’ input. Locate and select your CloudFront distribution (should begin with ‘www.’), then click the ‘Create’ button at the bottom part of your screen in the right pane. If your CloudFront distribution isn’t displayed it may still be in the creation phase. That process can take 30 minutes or more — you may have to exercise patience waiting for this.

Presuming you’ve successfully created a ‘www’ sub-domain record, navigate to CloudFront. Often, including while making this tutorial, even though my CloudFront distribution ‘Status’ is ‘In Progress’, I’m successfully able to access www.placesiwanttovisit.com — as displayed below. (On the right portion of the image you can see the page with SSL).

If you successfully completed setting all of this up, you deserve to be congratulated! You should be able to call your website’s non-SSL address, in my case http://www.placesiwanttovisit.com, and be redirected to the SSL version of the website — https://www.placesiwanttovisit.com. Furthermore, a page such as www.placesiwanttovisit.com/test.html should serve your index.html page’s content.

Bonus — redirect non-www traffic

We configured non-SSL www traffic to redirect to the SSL version of our website. However, non-www traffic (http://placesiwanttovisit.com) will not redirect. This is a common problem so I’ll fix it as well!

Navigate to S3 and create another Bucket. You’ll need the Bucket name to EXACTLY match your domain name without the www — in my case, that’s ‘placesiwanttovisit.com’. If this Bucket name isn’t available, you’ll have to contact AWS support — sorry, you can’t continue!

After entering your Bucket name, click the ‘Create’ button at the bottom left side of the screen.

Click on your newly created Bucket’s name, then on the next screen click on the ‘Properties’ tab. As done previously, click the ‘Static website hosting’ tile. This time, select the option for ‘Redirect requests’ and enter the ‘www’ version of your domain in the ‘Target bucket or domain’ input, in my case that’s www.placesiwanttovisit.com. Next, enter ‘https’ in the ‘Protocol’ input as pictured below, then click the ‘Save’ button.

DNS may take some time to propagate. Furthermore, if you attempted to visit the non-www version of your website earlier, it may be cached in your browser.

I experienced sporadic results getting this redirect to work in Chrome, most likely because I navigated to this domain (the non-www version) throughout the process of making this tutorial. Clearing the last hour of cache and browser history seemed to resolve this issue for me in Chrome.

Both FireFox and Safari consistently executed the redirect appropriately, most likely because I hadn’t attempted to call the non-www version of the domain in either of those browsers.

Another option you may consider trying is conducting an ‘Empty Cache and Hard Reload’. To display the option ‘Empty Cache and Hard Reload’ when you right mouse click on Chrome’s refresh button, ensure Developer Tools are available by clicking the ‘option+command+i’ keys on a Mac at the same time. Then you should be able to right click on the ‘refresh’ button in Chrome and select the ‘Empty Cache and Hard Reload’ option.

Congratulations if you made it this far and everything’s working! You’ve come a long way and hopefully learned something valuable.

If you enjoyed this tutorial and it helped you, clap for it, share it, and send me a note letting me know (my twitter is @aaronwht). If this tutorial helped you solve a business problem I would surely appreciate any donation amount you could offer.

--

--

--

AWS Certified JavaScript Expert — aaronwht.com

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Overcome the #1 Agility Killer

Java with Streams

Generics in Java

Why I Left Angular Behind for Vue.js

Python notebooks for R markdown lovers using Atom and Pweave

Four Ways to Improve Your Company’s Developer Documentation

GoodData.CN: Modern headless BI is now available (for free)

CS 373 Spring 2022: Kyle Kamka

Get the Medium app

Aaron White

Aaron White

AWS Certified JavaScript Expert — aaronwht.com

More from Medium

Application Security #3: How to find SSL Issues for your assets

Codify Your Endpoint Fleet

Securing MQTT Traffic with Cloud Internet Services

4 Best AWS DevOps Tools for Cloud Build and Deployment