Host a static website on AWS

This blog post is part of a series called How this blog is made.

In this post we will make the Hugo website available for others on the internet. To get fast and worldwide distribution of the static website we will use Amazon S3, Cloudfront and Route 53. For security, we will add a S3 Bucket Policy and a new AWS User with restricted access for the Github Actions.

We will be focusing on this part of the diagram:


Amazon Simple Storage Service (Amazon S3)#

In this section we will create a bucket where the static website files will be saved.

  1. Go to your AWS Console and to Amazon S3.
  1. Create a new Bucket:
    1. Give it a name: <Your-Bucket-Name>.
    2. Choose a region: <Your-Region>.
    3. Uncheck Block all public access and acknowledge the warning.
    4. Create your bucket.
  1. Open your new bucket and click Upload:
    1. Drag & Drop all the files in <Your-Repository>/public into the new window.
    2. Click Upload.
  1. Go to the Properties tab and here to Static website hosting:
    1. For Index document write: index.html
    2. For Error document write: 404.html
    3. Save your changes.
  1. In the Properties tab you will also find Tags. I like to tag all my resources for one project so here I go with the Key = project and Value = Save your changes.
  1. Next we will give permissions to read the bucket. Go to the Permissions tab and here to Bucket Policy:
    1. We will allow "Effect": "Allow" everyone "Principal": "*" to read "Action": "s3:GetObject the content of your bucket "Resource": "arn:aws:s3:::<Your-Bucket-Name>/*" under the condition, that their User-Agent equals <some-key-we-define>.
    2. Here we need a random key or password <some-key-we-define>. One easy way to get a random key on linux is:
    head /dev/urandom | tr -dc A-Za-z0-9 | head -c 48 ; echo ''
    1. Your policy now looks like this:
       "Version": "2012-10-17",
       "Statement": [
           "Sid": "AllowCloudfrontHeader",
           "Effect": "Allow",
           "Principal": "*",
           "Action": "s3:GetObject",
           "Resource": "arn:aws:s3:::<Your-Bucket-Name>/*",
           "Condition": {
             "StringEquals": {
               "aws:UserAgent": "<some-key-we-define>"
    1. Add the policy in the text area and save. The key we defined will later be used by Cloudfront to identify the Cloudfront-Distribution, we will create, as the only service to access the bucket.

Now we have a S3 bucket for our static website hosting which can only be accessed with the right User-Agent header.

AWS Certificate Manager (ACM)#

To serve the website over a secure connection with HTTPS we have to use a SSL Certificate which we will create in this section.

  1. Go to your AWS Console and to ACM
  1. Change your region to us-east-1. Only certificates in this region can be used by Cloudfront.
  2. Request a certificate:
    1. Request a public certificate.
    2. Fill in your domain: <Your-Domain>. If you like you can add subdomains like www.<Your-Domain> too.
    3. Choose a way to validate your request. If you are not sure which method is right for you, read the articles on Learn more.
    4. Tag your resource. I go with the Key = project and Value =
    5. After the Status of your certificate changed to Issued and the Validation status of your domains to Success, you can use your certificate with Cloudfront.


Cloudfront will serve the website using the SSL Certificate we created and cache the static data of your website near the users at AWS Edge Locations.

  1. Go to your AWS Console and to Amazon S3.
  1. Create a distribution:
    1. Click Create Distribution.
    2. Choose Web.
    3. Origin Domain Name: <Your-Bucket-Name>.s3-website.<Your-Region>
    4. Leave Origin Path empty and Orgin ID as it is.
    5. As Custom Header add:
      1. Header Name = User-Agent.
      2. Value = <some-key-we-define>.
    6. Viewer Protocol Policy = Redirect HTTP to HTTPS.
    7. Object Caching = Customize.
    8. Default TTL = 1800.
    9. Compress Objects Automatically = Yes.
    10. Alternate Domain Names (CNAMEs): add in separate lines <Your-Domain> and optional www.<Your-Domain>.
    11. Select Custom SSL Certificate and choose your new certificate.
    12. Click Create Distribution.
  2. On you Cloudfront dashboard at Distributions you can see your new distribution with an <CF-Distribution-ID> and a <CF-Domain-Name>.
  3. Open your new Cloudfront distribution. On the tab General you can see your <CF-Distribution-ARN>. Go to the tab Tags and tag your resource. I go with the Key = project and Value =

Now we have a Cloudfront distribution which directs traffic to your S3 bucket. This Cloudfront distribution is the only service that knows your secret key <some-key-we-define> and can access your bucket with the static website. You can test your permissions by opening <Your-Bucket-Name>.s3-website.<Your-Region> Your request should be denied. If you try <CF-Domain-Name> you can access your new website over the internet.

Route 53#

To reach our new Cloudfront distribution with our own domain <Your-Domain>, we will use the DNS service of Route 53 to point <Your-Domain> to <CF-Domain-Name>.

  1. Go to your AWS Console and to Route 53.
  2. On the left side on Hosted zones and click Create Hosted Zone:
    1. Domain Name: <Your-Domain>.
    2. Click Create.
  3. Open your new hosted zone.
  4. Here you see four addresses at Type = NS who look like Copy all four addresses and put them at the DNS nameserver settings wherever you have registered your domain like Namecheap or GoDaddy. It might take a while until these settings are updated (8h-24h). You can check your domain settings at
  1. Next create a Record Set:

    1. Click Create Record Set.
    2. Leave the Name blank.
    3. Type = A.
    4. Alias = Yes.
    5. Alias Target = <You-Cloudfront-Distribution>
    6. Click Create.
  2. Optional for your www.<Your-Domain>:

    1. Click Create Record Set.
    2. Name = www.
    3. Type = A.
    4. Alias = Yes.
    5. Alias Target = <You-Cloudfront-Distribution>
    6. Click Create.

Now <Your-Domain> will be routed to <CF-Domain-Name> and from here to <Your-Bucket-Name>.