Static Hosting With AWS and user-friendly URLs

Feature Image

Recently I started working on this very same website, and I wanted to avoid using something like Wordpress, and don’t get me wrong, Wordpress is great, but I wanted to use AWS for hosting, and the cost wasn’t just worth it. So I decided to build a static website and use the power of S3 and Cloudfront to host it.

Here is were Hugo came into the picture. It was a great way to build my website and blog as static content. I was super happy with it and I had it running locally in no time, but then it was time to deploy it to AWS and for the most part, it just worked, except for a minor detail, pretty URLs were not working at all.

In this post, I’ll cover how to configure and deploy your Hugo website (or any other static website) using AWS S3 and Cloudfront.

Build your website

I won’t go here into details and there are a lot of tutorials on how to do this with Hugo or any other tool. But if you need a hint, I used this tutorial from the Hugo official documentation that is a great start.

Configuring the platform

In this section, we will cover how to set up your S3 bucket, create your Cloudfront distribution, and deploy your static website.

Create the S3 bucket

  1. Sign in to the AWS Management Console and open the Amazon S3 console at https://console.aws.amazon.com/s3/.

  2. Choose Create Bucket and complete the form as follows:

    1. Enter the Bucket Name
      Note that the name must be DNS-complaint and that is unique across all of Amazon S3.

    2. Select the region
      Choose a Region close to you to minimize latency and costs and address regulatory requirements.

    3. Select Block all public access
      This option is currently the default.

    4. Click Create Bucket

Create the Cloudfront Distribution

  1. Open the Amazon Cloudfront console at https://console.aws.amazon.com/cloudfront/home

  2. Choose Create Distribution and complete the wizard as follows:

    1. Choose Web as the delivery method for your content.

    2. Complete the form and in addition to the distribution settings that you need for your use case, enter the following:

      1. For Origin Domain Name, select the bucket that you created.
      2. For Restrict Bucket Access, select Yes.
      3. For Origin Access Identity, select Create a New Identity.
      4. For Comment, you can choose to keep the default value. Or, you can enter a custom label for the OAI.
      5. For Grant Read Permissions on Bucket, select Yes, Update Bucket Policy.
    3. If you don’t want to use SSL (HTTPS) for your website, proceed to the next step. To use SSL for your website, for SSL Certificate, you can select the Default CloudFront Certificate or a Custom SSL Certificate. Or, you can choose Request or Import a Certificate with ACM to request a new certificate.

    4. Choose Create Distribution

    5. Update the DNS records for your domain to point your website’s CNAME to your CloudFront distribution’s domain name. You can find your distribution’s domain name in the CloudFront console in a format that’s similar to d1234abcd.cloudfront.net.

    6. Wait for your DNS changes to propagate and for the previous DNS entries to expire.

Deployment

Deployment from Hugo to AWS is very straight forward and well documented in the official docs.

For deploying I’m using a bash script which simplifies my workflow, here is how it works

./deploy.sh

Done! it’s deployed. Here is the code to the script

#!/bin/bash
echo "Deleting old files"
rm -rf public
echo "Building"
hugo
hugo deploy --maxDeletes -1 --invalidateCDN
echo "Deployment Completed!"

Don’t forget to give the script execution rights.

The Problem

After some testing you may realize that some links don’t work, they give an “Access Denied” Error. So what is going on? Well… When we generate a website with Hugo (or many of the other static CMSs out there) you will find that each page gets generated as follows: /about/index.html, but that’s probably not how you set up your links in the app, you probably had something like /about.

Why is Cloudfront not resolving the directories and using the Default root page? The problem lies within hosting a site on S3 through CloudFront. CloudFront uses the S3 API to find files, and the API is a bucket with file references, imitating a folder structure. Enabling website hosting for an S3 bucket will allow you to redirect traffic to the S3 HTTP endpoint which simulates a web server, but when using CloudFront in front of it you are not allowed to do that anymore. It effectively disables the Apache- or Nginx-like behavior where you can assign a directory index. CloudFront does not know you are referring to a folder.

The Solution

The Solution: Lambda@Edge

The Solution: Lambda@Edge

After looking around the internet for a while I found a solution using Lambda@Edge to generate redirects which we can control to append the /index.html to the user entered URLs.

What is Lambda?

For those that don’t know yet, AWS Lambda is a service that lets you run pieces of code on demand, without the need to manually launch a server. In their own words:

AWS Lambda lets you run code without provisioning or managing servers. You pay only for the compute time you consume.

I worked in the past quite a bit wit Lambda, however I never used this functionality with Cloudfront, nor I knew it was even possible.

Now it’s possible to write your function and do all the configurations manually, and that’s what I did, however after looking some more I found an easier way using standard-redirects-for-cloudfront repository.

Configuration

  1. Go to Serverless Application Repository.

  2. Press the Deploy button.

  3. It opens a new modal with the description of the app, press Deploy again to create all the resources.

  4. After it has been created, locate the button View CloudFormation Stack or go directly to the Cloudformation Console.

  5. In the Resources tab, locate the AWS::IAM::Role and open the Physical ID, it will open up the IAM console.

  6. Go to Trust Relationship tab and choose Edit the trust relationship to allow CloudFront to execute this function as a Lambda@Edge function., set the policy to:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Service": [
              "lambda.amazonaws.com",
              "edgelambda.amazonaws.com"
            ]
          },
          "Action": "sts:AssumeRole"
        }
      ]
    }
    
  7. Go back to the Cloudformation’s Stack Detail page and in the Output tab, locate the key StandardRedirectsForCloudFrontVersionOutput and note down its Value (it will look something like: arn:aws:lambda:us-east-1:XXXXXXXXXXX:function:aws-serverless-repository-StandardRedirectsForClou-XXXXXXXXXXXX:2 ). We will use it in the next steps as this is the ARN (Amazon Resource Name) for the Lambda function that we will use in Cloudfront.

  8. Go back to the CloudFront console, select your distribution

  9. Go to the Behaviour tab and edit the default Behavior.

  10. Now we use the Lambda function, in Lambda Function Association select Event Type/Origin Request and enter the Lambda function’s StandardRedirectsForCloudFrontVersionOutput ARN value from the previous step.

Wait for a few minutes for Lambda@Edge to do its thing

Wait for a few minutes for Lambda@Edge to do its thing

Now it just works!

Congratulations, your static website is now working properly!

Join the Free Newsletter

A free, weekly e-mail with the best new articles.

We won't send you spam. Unsubscribe at any time.

Contribute

If you like this article and you want to support my work, you can: