Amazon Web Services – Host Hugo Static Website on S3 and CloudFront

Posted by Lucas Jackson on Friday, July 9, 2021


This post will cover the full process end to end to get your Hugo static website hosted on S3 and CloudFront. This post will cover the certificate generation process and the automated deployment of your website using CI/CD from GitLab.

Create S3 Bucket

  1. Log into AWS Console and go to S3.
  2. Create a new bucket, select an appropriate location and give your bucket a relevant name. Enable encryption (not required but I always like to enable encryption where possible) and block all public access to the bucket.

  1. Go to your newly created bucket and click Properties. Scroll down to the bottom section Static website hosting and click Edit. We are setting it like this since we want CloudFront to handle all our traffic, if someone hits the S3 bucket directly they will be forced back to our website.

Create a Certificate

Amazon has it’s own Certificate Authority (CA), AWS Certificate Manager (ACM) provides the ability to create certificates and automate their renewal. When you create your certificate makes sure you use US East (N. Virginia) Region, this is a requirement when using CloudFront.

  1. Go to AWS Console and go to Certificate Manager.
  2. Click Get Started on Provision Certificates and create a Public Certificate.

  1. Add your domain names, in my case I am adding * and

  1. Select a validation method, my preference is DNS validation since I own the domain.

  2. Review your request and then click Confirm and request.

  3. You will be asked to add a CNAME record in DNS. Once you’ve added the record, the certificate will be issued by Amazon. Validation occurs very quickly after you’ve added the CNAME record. Going forward you need to leave this CNAME record in DNS so Amazon can issue certificates.

After you’ve integrated this certificate with CloudFront, the renewal eligibility will change to eligible.

Create CloudFront Distribution

  1. Go to AWS Console and go to CloudFront.
  2. Create a new Distribution.

  1. Select your S3 bucket from earlier as the Origin domain.
  • Set S3 bucket access to Yes use OAI (bucket can restrict access to only CloudFront).
  • Click Create new OAI to create a new access identity.
  • Set Bucket policy to Yes, update the bucket policy.
  • Set Viewer Protocol Policy to Redirect HTTP to HTTPS.
  • Add Alternate domain name (CNAME) as required.
  • Select your Custom SSL certificate that we created in the previous step.
  • Set Default Root Object to your landing page document (eg. index.html).

  1. Create your distribution, it will take some time to deploy it.

  2. If you’re curious you can check out your S3 bucket policy now, it’s been updated to provide access to CloudFront.

Setup CI/CD for GitLab Integration

Create User

Before we can configure the GitLab pipeline, we need to create a new user and assign a permissions policy so it can access and modify our S3 bucket.

  1. Go to AWS Console and go to IAM > Users.

  2. Click Add User.

  1. Click Create Policy, a new browser tab will open.

  1. Create the policy using the JSON document below, making the necessary alterations to bucketName id and distId for your scenario. Give the policy whatever name you desire.
    "Version": "2012-10-17",
    "Statement": [
            "Sid": "getPutDelS3",
            "Effect": "Allow",
            "Action": [
            "Resource": "arn:aws:s3:::<bucketName>/*"
            "Sid": "listS3",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::<bucketName>"
            "Sid": "invalidateCloudFront",
            "Effect": "Allow",
            "Action": [
            "Resource": "arn:aws:cloudfront::<id>:distribution/<distId>"
  1. Go back to the Add User tab, refresh the policies and attach your newly created policy. Create the account.

  1. Take note of the Access key ID and the Secret access key, these will be used when we setup the GitLab pipeline.

Create Pipeline

  1. Create a new GitLab project or use an existing one.

  2. Go to the project, from the left menu bar select Settings > CI / CD.

  3. Scroll down to variables and add the variables below with the Access key ID and the Secret access key captured previously.


  1. If you have no files in your project add a test file.

  2. Create a new file named .gitlab-ci.yml and update AWS_DEFAULT_REGION, BUCKET_NAME and CLOUDFRONT_DIST_ID.

  - build
  - deploy

  AWS_DEFAULT_REGION: ca-central-1
  BUCKET_NAME: <bucketName>

  image: klakegg/hugo:0.84.4-ubuntu-ci
  stage: build
    - hugo -v
      - public
    - master

  image: python:latest
  stage: deploy
    - buildHugo
    - pip install awscli
    - aws configure set preview.cloudfront true
    - aws s3 sync ./public s3://$BUCKET_NAME --delete
    - aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DIST_ID --paths "/*"
    - master
  1. Your GitLab pipeline will now execute and copy your repository to your S3 bucket.

Validate Your Deployment

  1. Test your CloudFront distribution url to ensure everything is working as anticipated.

  2. Add your CNAME record in DNS to point to your CloudFront distribution.

  3. Verify your CloudFront has an invalidation, this is created by the GitLab pipeline. Go to your CloudFront distribution and click Invalidations, you should see a job ID there.

Additional Configuration

Error Pages

When someone tries to access a url on your website that does not exist, they will get a nice forbidden message.

To make this nice we can add error pages to the CloudFront distribution.

  1. Go to your CloudFront distribution and click Error pages.

  2. Click Create custom error response.

  • Set HTTP error code to 403: Forbidden.
  • Set Customize error response to Yes.
  • Update Response page path to /path-of-your-error-page.html.
  • Set HTTP Response code to 404: Not Found.

  1. Repeat this process for any other HTTP error codes you want to handle.

Re-Write Web Request Using Lambda@Edge

If you want to perform re-writes for some specific use case for your static website you can follow this article. It works quite well, tailor it to your requirements.