How To Host A Static HTML Website On AWS S3

By: James Malin
May 19, 2021

Overview

In this post we will discuss how to set up a static HTML website in S3. We have even provided a little bonus to help you quickly get started!

Note: If you want us to help you connect your domain name or any web design or development services check out this page: Web Development Services.

Prerequisites

  1. Set up AWS Account

Quick & Sweet

For those of you that aren't interested in the technical how to and just want to launch your free HTML website on S3 use the launch stack button below.



Pretty cool huh?

Note: This does not include connecting your domain or the HTTPS protocol. You would need to add Route53 and a SSL certificate into the mix.

The Breakdown

The CloudFormation template above has a lot of operations running to get the end result. Here is the breakdown of the CloudFormation template:

  1. Create S3 Bucket
  2. Configure S3 Bucket Policy
  3. Create CloudFront Distribution
  4. Create IAM user with Access Keys

Create S3 Bucket

We need to first create the S3 bucket to contain the website HTML.

	# Create the bucket to contain the website HTML
	S3Bucket:
		Type: 'AWS::S3::Bucket'

Configure S3 Bucket Policy

We also want to configure the bucket policy.

  # Configure the bucket as a CloudFront Origin
  ReadPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Statement:
        - Action: 's3:GetObject'
          Effect: Allow
          Resource: !Sub 'arn:aws:s3:::${S3Bucket}/*'
          Principal:
            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId

Create CloudFront Distribution

Then we need to configure the bucket as a CloudFront Origin which includes a read policy, CloudFront origin access identity, and CloudFront distribution.

  CloudFrontOriginAccessIdentity:
    Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: !Ref S3Bucket
  CloudFrontDistribution:
    Type: 'AWS::CloudFront::Distribution'
    Properties:
      DistributionConfig:
        CustomErrorResponses:
        - ErrorCode: 403 # not found
          ResponseCode: 404
          ResponsePagePath: !Ref ErrorPagePath
        DefaultCacheBehavior:
          AllowedMethods:
          - GET
          - HEAD
          - OPTIONS
          CachedMethods:
          - GET
          - HEAD
          - OPTIONS
          Compress: true
          DefaultTTL: 3600 # in seconds
          ForwardedValues:
            Cookies:
              Forward: none
            QueryString: false
          MaxTTL: 86400 # in seconds
          MinTTL: 60 # in seconds
          TargetOriginId: s3origin
          ViewerProtocolPolicy: 'allow-all'
        DefaultRootObject: !Ref DefaultRootObject
        Enabled: true
        HttpVersion: http2
        Origins:
        - DomainName: !GetAtt 'S3Bucket.DomainName'
          Id: s3origin
          S3OriginConfig:
            OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}'
        PriceClass: 'PriceClass_All'

Create IAM user with Access Keys

We want to create an IAM user that can publish the website. This will require obtaining a public and private access key.

	PublishUser:
    Type: 'AWS::IAM::User'
    Properties:
      Policies:
        - PolicyName: !Sub 'publish-to-${S3Bucket}'
          PolicyDocument:
            Statement:
            - Action: 's3:*'
              Effect: Allow
              Resource: 
              - !Sub 'arn:aws:s3:::${S3Bucket}'
              - !Sub 'arn:aws:s3:::${S3Bucket}/*'
  PublishCredentials:
    Type: 'AWS::IAM::AccessKey'
    Properties:
      UserName: !Ref PublishUser

All Together Now

Here is the full code for the CloudFormation template.

	AWSTemplateFormatVersion: '2010-09-09'
Description: 'Static website hosting with S3 and CloudFront'
Parameters:
  DefaultRootObject:
    Description: 'The default path for the index document.'
    Type: String
    Default: 'index.html'
  ErrorPagePath:
    Description: 'The path of the error page for the website (e.g. /error.html). Must be a root-relative path.'
    Type: String
    Default: '/404.html'
Resources:
  # Create the bucket to contain the website HTML
  S3Bucket:
    Type: 'AWS::S3::Bucket'

  # Configure the bucket as a CloudFront Origin
  ReadPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Statement:
        - Action: 's3:GetObject'
          Effect: Allow
          Resource: !Sub 'arn:aws:s3:::${S3Bucket}/*'
          Principal:
            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
  CloudFrontOriginAccessIdentity:
    Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: !Ref S3Bucket
  CloudFrontDistribution:
    Type: 'AWS::CloudFront::Distribution'
    Properties:
      DistributionConfig:
        CustomErrorResponses:
        - ErrorCode: 403 # forbidden
          ResponseCode: 404 # not found
          ResponsePagePath: !Ref ErrorPagePath
        DefaultCacheBehavior:
          AllowedMethods:
          - GET
          - HEAD
          - OPTIONS
          CachedMethods:
          - GET
          - HEAD
          - OPTIONS
          Compress: true
          DefaultTTL: 3600 # in seconds
          ForwardedValues:
            Cookies:
              Forward: none
            QueryString: false
          MaxTTL: 86400 # in seconds
          MinTTL: 60 # in seconds
          TargetOriginId: s3origin
          ViewerProtocolPolicy: 'allow-all'
        DefaultRootObject: !Ref DefaultRootObject
        Enabled: true
        HttpVersion: http2
        Origins:
        - DomainName: !GetAtt 'S3Bucket.DomainName'
          Id: s3origin
          S3OriginConfig:
            OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}'
        PriceClass: 'PriceClass_All'

  # Create an IAM user with Access Keys to enable automated deployment of the website to this bucket
  PublishUser:
    Type: 'AWS::IAM::User'
    Properties:
      Policies:
        - PolicyName: !Sub 'publish-to-${S3Bucket}'
          PolicyDocument:
            Statement:
            - Action: 's3:*'
              Effect: Allow
              Resource: 
              - !Sub 'arn:aws:s3:::${S3Bucket}'
              - !Sub 'arn:aws:s3:::${S3Bucket}/*'
  PublishCredentials:
    Type: 'AWS::IAM::AccessKey'
    Properties:
      UserName: !Ref PublishUser
Outputs:
  BucketName:
    Description: 'S3 Bucket Name'
    Value: !Ref S3Bucket
  AccessKeyId:
    Description: 'S3 Access Key'
    Value: !Ref PublishCredentials
  AccessKeySecret:
    Description: 'S3 Secret Key'
    Value: !GetAtt PublishCredentials.SecretAccessKey
  DistributionId:
    Description: 'CloudFront Distribution ID'
    Value: !Ref CloudFrontDistribution
  Domain:
    Description: 'Cloudfront Domain'
    Value: !GetAtt CloudFrontDistribution.DomainName

Conclusion

That's a wrap folks. If you enjoyed this tutorial, please let us know!

Need expert help? Work directly with James Malin now.