How to Host a Secure Static Website using AWS S3 and CloudFront
Tags: aws, s3, cloudfront, security
Published: 2023-07-09 19:11:32

## Table of contents
- [Introduction](#introduction)
- [How to host a static website in a secure way](#how-to-host-a-static-website-in-a-secure-way)
    - [Step 1: Create static website](#step-1-create-static-website)
    - [Step 2: Create S3 bucket](#step-2-create-s3-bucket)
    - [Step 3: Upload our website to S3](#step-3-upload-our-website-to-s3)
    - [Step 4: Create CloudFront Distribution](#step-4-create-cloudfront-distribution)
    - [Step 5: Update bucket policy](#step-5-update-bucket-policy)
    - [Step 6: Let’s check our site](#step-6-lets-check-our-site)
    - [Step 7: Create custom error documents](#step-7-create-custom-error-documents)
    - [Step 8: Get real 404](#step-8-get-real-404)
- [Lessons Learned](#lessons-learned)

## Introduction

In this easy-to-follow guide, I will show you how to put your static website on the internet using Amazon's S3 and CloudFront. We'll keep things secure, sticking to a blend of best practices and AWS recommendations. We'll cover the basics and also get into some of the details, making it simple for you to host your website. Let's dive in!

## How to host a static website in a secure way

In order to finish this tutorial, we need to create an S3 bucket and a CloudFront distribution. We will also explore the bucket policies and Origin Access Control (OAC). Once you're done with the tutorial, deleting these resources is a good idea to avoid unnecessary AWS charges.

### Step 1: Create static website

To host a static website, we need to do a few things before we can get started. We must create it first. Don't worry. Creating a static site can be complicated, but we'll keep things simple.

To begin, we'll start with some basic HTML. We don't want to go into too much detail at this point. Let's focus on just a few files that are essential for now.

**index.html**

```
<!DOCTYPE html>
<html lang=en>
  <head>
    <meta charset=utf-8>
    <title>index</title>
  </head>
  <body>
    <h1>Hello World!</h1>
  </body>
</html>
```

**403.html**

```
<!DOCTYPE html>
<html lang=en>
  <head>
    <meta charset=utf-8>
    <title>403</title>
  </head>
  <body>
    <h1>403</h1>
  </body>
</html>
```

**404.html**

```
<!DOCTYPE html>
<html lang=en>
  <head>
    <meta charset=utf-8>
    <title>404</title>
  </head>
  <body>
    <h1>404</h1>
  </body>
</html>
```

### Step 2: Create S3 bucket

Let's begin by navigating to the AWS Management Console. Take advantage of the search box positioned at the top of the screen for a faster route. By typing in "S3", you can quickly locate the S3 service amidst the numerous services AWS offers.

Before we delve into creating a new S3 bucket, it's essential to remember a couple of crucial points:
1. Each S3 bucket must be created in a specific geographical region. This region can be chosen based on factors such as data governance requirements, proximity to the majority of your users, or cost.
2. Each bucket name must be globally unique across all existing bucket names in Amazon S3. This is because the bucket namespace is shared by all AWS accounts worldwide. Therefore, it's a good idea to devise a unique name that relates to its purpose.

In this walkthrough, I've opted for the name "one-more-secure-website". This name encapsulates the purpose of our bucket - to hold assets for a secure website - and also adheres to the requirement of global uniqueness. Now, we're ready to create our S3 bucket using this name.

![S3 bucket creation screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e9bgrp39x94bzexijw53.png)

You can now adjust various settings, including versioning, server-side encryption, and access permissions. However, you can leave these settings at their default values for this tutorial. Remember, these configurations can be modified later if required.

Once you're satisfied with the settings, finalize the process by clicking on the "Create bucket" button at the bottom of the page. Your new bucket should now be successfully created in your designated AWS region.

### Step 3: Upload our website to S3

We need to move some files to a place called an S3 bucket. You can do this two ways: using the AWS Management Console or running commands from your computer using CLI or Command-Line Interface.

![Files successfully uploaded to S3 bucket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fph78qbej1nmvwv66ybq.png)

If you're okay with typing commands, it's pretty easy. Keep in mind that you need to install the AWS CLI in order to use it. For example, if you have a file called "index.html" and you want to put it in your bucket, you'd type something like this:

```
aws s3 cp index.html s3://{bucket}/index.html
```

### Step 4: Create CloudFront Distribution

This is the most exciting part. You might have noticed that we haven't made any changes to the S3 settings, yet we still want to host a static website. The reason is that CloudFront has all the necessary features to accomplish that.

Open up the AWS console, the control panel for all Amazon's tools. Type "CloudFront" in the search box on top. It'll take you right to the CloudFront tool.

![Amazon CloudFront welcome screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zls1d57kca63eklvdi7y.png)

There's a button that says "Create a CloudFront distribution". Click it. This will start the process of setting up how your website data gets sent out.

You'll be asked to select your "origin domain". That's where your website data is coming from - in our case, it's our S3 bucket, the storage space we mentioned earlier.

![CloudFront Distribution creation screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p3brz5vq99ybi4b18es5.png)

Look for the "Origin access control settings" area. Go for the "Origin access control settings (recommended)" option and click "Create control setting". It's okay to stick with what's already there, no need to change anything. Then click "Create".

![The Origin Access Control Creation Form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tfr3t9tbru1v8skk56i4.png)

Next, we have to sort out the "Default cache behavior". This is how CloudFront stores and sends your website data. Make sure you change "Viewer protocol policy" to "Redirect HTTP to HTTPS". That's just a fancy way of saying it makes sure anyone visiting your site has a secure connection. Leave everything else as it is.

![Default cache behavior](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eicn4mh58x6bsduijudq.png)

You'll see a "Web Application Firewall (WAF)" section. We're going to skip it, so select "Do not enable security protections".

![Web Application Firewall settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h1guvyepitujthwwdc14.png)

Almost there! In the "Settings" part at the bottom, make sure the "Default root object" says "index.html". That's just telling CloudFront what to show when someone visits your website.

![General CloudFront settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m7iwj279d8j5055secdk.png)

To finish off, scroll down and click "Create distribution". And there you go! You've set up how your website gets sent to viewers with CloudFront.

### Step 5: Update bucket policy

After setting things up, you'll get two messages from AWS. The first one basically says, "Good job! You've created the distribution successfully!" The second one suggests you update your S3 bucket policy, which is like a set of rules for your S3 bucket. This update is needed so CloudFront can read what's in your S3 bucket.

![CloudFront Distributions screen displays 2 messages: one confirming successful distribution creation, the second advising an S3 bucket policy update](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s50ydyeamqwyjrhdmsrz.png)

Here's how to do that:

1. Look for and click on the "Copy Policy" button, then click on the "Go to S3 bucket permissions to update policy" button. When you click this, it'll take you directly to your S3 bucket's permissions tab.
2. Find the "Bucket Policy" section and click "Edit". You'll see a field where you can paste the policy you got earlier.
3. After inserting the policy, hit "Save changes" to lock in these new rules.

![S3 bucket policy enabling CloudFront distribution access to files](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/di0vmxn2oqsorn0no7nf.png)

Once that's done, you'll have to wait a bit. The CloudFront distribution is getting ready, which is like preparing your content for delivery. You've created a strong base for hosting a safe, static site.

### Step 6: Let’s check our site

To check out the site we've set up, copy the distribution domain we've given and put it into browser's address bar. Upon opening it, you should see our index page.

![CloudFront distribution's domain displaying our index.html](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rhrdu4xhovci1rbatldl.png)

You can also try typing in the address without the "https://" part at the beginning, just using "http://" instead. If you do this, don't worry, CloudFront will automatically guide you to the secure version of the site (the one starting with "https://"). You might notice a message saying "301 Moved Permanently", that just means CloudFront moved you to the secure page, which is a good thing!

![A request was made to our CloudForm Distribution domain, which resulted in a 301 redirect from the HTTP version to the HTTPS version](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p22fv72hoghw7mpm3x93.png)

### Step 7: Create custom error documents

Imagine you've got a bucket and it only contains three files: an index, a "403" item, and a "404" item. Now, pretend you're looking for something that isn't there, like a document called "another.html". You'd probably expect to be told, "Sorry, can't find that" – or in internet language, you'd expect a '404 error'. Let's check if that's what actually happens.

![A request to a missing file that displays an XML error message stating AccessDenied with error code 403](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gj3s1gtmxbagfe4nvbm9.png)

Oops! Instead of a simple "can't find that" message, we get a weird XML document and a 403 error. A bit strange, huh? You'd think we'd get a 404 error for something missing, not a 403 error. But let's not worry about that right now. Let's focus on making this whole thing less confusing for our users.

First, click on the tab that says "Error Pages". Then select the option that says "Create custom error response", and fill in the information for the 403 error. Hit the Save button when you're done.

![Custom error page form for CloudFront distribution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mhz3xlg0n4uuox2ift2h.png)

Now, try to open "another.html" again. This time, you should see our custom error page instead of that strange XML. Much better, right?

![A request to a missing file that displays a custom error page with code 403](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9do46fm0k1sxh3j84bze.png)

### Step 8: Get real 404

Ever wondered why you sometimes get a 403 error from S3 when a file isn't found? It's because of security reasons.

Amazon S3 gives you an "AccessDenied" error when you ask for a file that doesn't exist, and you don't have the permission to see all the files in that bucket. It's a neat way of keeping things private, as it doesn't let anyone know if the file you're looking for actually exists or not.

So, what if we allow CloudFront to have the "s3:ListBucket" permission? This lets it see all the files in the bucket. Here's how you do it:

1. Go to the permissions tab of the S3 bucket.
2. Find the statement that you pasted previously and copy it.
3. Please make a copy of that statement, but change it slightly. First, change the action to allow "s3:ListBucket". Then, make sure you remove "/*" from the end of the resource name. This new permission should be given to the bucket itself, not its contents.

Original policy:

```
{
	"Version": "2008-10-17",
	"Id": "PolicyForCloudFrontPrivateContent",
	"Statement": [
		{
			"Sid": "AllowCloudFrontServicePrincipal",
			"Effect": "Allow",
			"Principal": {
				"Service": "cloudfront.amazonaws.com"
			},
			"Action": "s3:GetObject",
			"Resource": "arn:aws:s3:::one-more-secure-website/*",
			"Condition": {
				"StringEquals": {
					"AWS:SourceArn": "arn:aws:cloudfront::{account_number}:distribution/E3QFHDB3Z8A00A"
				}
			}
		}
	]
}
```

Modified version:

```
{
	"Version": "2008-10-17",
	"Id": "PolicyForCloudFrontPrivateContent",
	"Statement": [
		{
			"Sid": "AllowCloudFrontServicePrincipal",
			"Effect": "Allow",
			"Principal": {
				"Service": "cloudfront.amazonaws.com"
			},
			"Action": "s3:GetObject",
			"Resource": "arn:aws:s3:::one-more-secure-website/*",
			"Condition": {
				"StringEquals": {
					"AWS:SourceArn": "arn:aws:cloudfront::{account_number}:distribution/E3QFHDB3Z8A00A"
				}
			}
		}, {
			"Sid": "AllowCloudFrontServicePrincipal",
			"Effect": "Allow",
			"Principal": {
				"Service": "cloudfront.amazonaws.com"
			},
			"Action": "s3:ListBucket",
			"Resource": "arn:aws:s3:::one-more-secure-website",
			"Condition": {
				"StringEquals": {
					"AWS:SourceArn": "arn:aws:cloudfront::{account_number}:distribution/E3QFHDB3Z8A00A"
				}
			}
		}
	]
}
```

Once you've done that and refresh the page, you'll see a different error message - "NoSuchKey". This means that CloudFront can now tell us when the file we're looking for isn't in our bucket.

![A request to a missing file that displays an XML error message stating NoSuchKey with error code 404](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/unyjui6hnt7v7whf7bnz.png)

To finish things off, let's add another custom error page for when a 404 error occurs. Once you've done that and refresh the page, you'll see our custom HTML message instead of the standard error message.

![A request to a missing file that displays a custom error page with code 404](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zzzws5bqoukiedtou7c6.png)

## Lessons Learned

I hope you've found this article about hosting a static website using S3 and CloudFront helpful and perhaps even learned something extra. Let's sum it all up:

- You now have the skills to host a website securely without leaning on the S3 static website feature and making a bucket public.
- Every architectural choice has its pros and cons. Navigating through the world of AWS, you might find many paths leading to the same destination. The obvious path isn't always the wisest.
- In this tutorial, we've crafted a simple infrastructure. While it doesn't cover all the aspects of a real-world project, like versioning, logs, or complex topics such as CORS, it gives you a solid foundation to build upon.

I hope you enjoyed this journey with me. Feel free to share it, leave your thoughts, or ask questions if you found it helpful. Let's keep the conversation going. Until next time, happy coding!