Joe Gilmore

20 mins read

AWS Launching a ServerLess Cloudfront Distribution

This article shows you how to can spin up a distribution in AWS Cloudfront using the Serverless Framework

AWS Launching a ServerLess Cloudfront Distribution

Github Repo for this

You can find all the code required for this blog articles here on my github repo

What are we building here?

We are going to make a script that spins up a private S3 Bucket, and then also adds a Cloudfront (CF) Distribution that points to that bucket.

We will also make sure that we can do the following:

  • Be able to use a Custom Domain Alias e.g.
  • Use an SSL Certificate from ACL (AWS Certificate Manager)
  • Use a domain we have in Route53 and auto assign a subdomain as an Alias to our CF Distribution
  • Make sure that the CloudFront Distribution CORS are only allowed from a specific URL or an entire domain and subdomains https://* or anywhere on the internet *
  • Upload some test files to the S3 Bucket and make sure that they are only accessible via the CF Distribution
  • Configure a custom 404 response that simply returns an empty JSON obhject {}

Once we have spun up our S3 Bucket and CloudFront Distribution - we should be able to add files to the S3 Bucket and then access them via the CF Distro. We shouldn't be able to view them via S3 directly, and they should have the correct CORS access headers for viewing only via our specified domain!

SSL Certificates

First make sure that you have an SSL certification for your domain and subdomain (perhaps even a wildcard certificate is best) - and that it is stored in AWS Certificate Manager (ACM). You will need to copy the ARN of the certificate to use in the CloudFront Distribution.


This script will also create a Route53 record for the domain and subdomain that you specify and point it to the CloudFront Distribution.

Editing .env.example

You will need to copy the .env.example file to .env and then edit the values to match your own.

# Set the price class to "Use Only North America and Europe" (PriceClass_200 is also europe etc, PriceClass_all is basically everywhere AWS supports)
# Allowed Origins  Can be either a single * or https://* or - Read more here - if you wish to use multiple then edit the serverless.yml file itself

# optional 1 - using a domain alias (also comment out lines 77-81 of serverless.yml if not using this)
MY_ACM_CERTIFICATE_ARN=arn:aws:acm:us-east-1:1111111111:certificate/5555555-1111-2222-3333-b82099e490bd # Your SSL Cert found in

# optional 2 - get route 53 to assign our domain alias automatically (also comment out lines 100-117 of serverless.yml if not using this)

Please note - You don't have to use a .env file and you could simply edit the serverless.yml file directly, but I find it easier to use a .env file for my own values and then edit the serverless.yml file directly for any other values I need to change.

The ServerLess Script

service: ${env:SERVICE_NAME}
useDotenv: true

  name: aws
  profile: ${env:IAM_PROFILE}
  region: ${env:MY_REGION}
  runtime: nodejs18.x
    # The S3 Bucket
      Type: AWS::S3::Bucket
        BucketName: ${env:MY_BUCKET}
        AccessControl: Private # Set the bucket access control to private

            - AllowedOrigins:
                - ${env:ALLOWED_ORIGINS}
                - HEAD
                - GET
                - "*"

    # The S3 Bucket Policy
      Type: AWS::S3::BucketPolicy
          Id: MyPolicy
          Version: "2012-10-17"
            - Sid: AllowCloudFrontServicePrincipalReadOnly
              Effect: Allow
              Principal: { "Service": "" }
                - s3:GetObject
              Resource: !Sub arn:aws:s3:::${MyS3Bucket}/*
                  "aws:SourceArn": !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${MyCloudFrontDistribution}
          Ref: MyS3Bucket
      Type: AWS::CloudFront::OriginAccessControl
          Name: ${env:MY_BUCKET_ORIGIN_ID}
          OriginAccessControlOriginType: s3
          SigningBehavior: always
          SigningProtocol: sigv4

    # The Cloud Front Distribution
      Type: AWS::CloudFront::Distribution
          Enabled: true
          IPV6Enabled: true
          Comment: ${env:MY_DISTRO_COMMENT} # Add a description to the CloudFront distribution
          PriceClass: ${env:MY_DISTRO_PRICE_CLASS} # Set the price class to "Use Only North America and Europe"

          #optional 1 - Custom Domain alias - Comment out the 5 lines below if you don not want to use the domain alias and instead just use
            - ${env:MY_DOMAIN_ALIAS} # Replace with your desired CloudFront distribution alias (Make sure you have a cert in "AWS Certificate Manager (ACM)" in the us-east-1 region)
            AcmCertificateArn: ${env:MY_ACM_CERTIFICATE_ARN} # Replace with your ACM certificate ARN
            SslSupportMethod: sni-only

            - DomainName: ${env:MY_BUCKET}.s3.${env:MY_REGION}
              Id: ${env:MY_BUCKET_ORIGIN_ID}
              S3OriginConfig: {}
              OriginAccessControlId: !GetAtt OriginAccessControl.Id
            AllowedMethods: [GET, HEAD, OPTIONS]
            TargetOriginId: ${env:MY_BUCKET_ORIGIN_ID}
            CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # No caching
            OriginRequestPolicyId: 88a5eaf4-2fd4-4709-b370-b4c650ea3fcf # CORS-S3
            ResponseHeadersPolicyId: 67f7725c-6f97-4210-82d7-5512b31e9d03 # SecurityHeadersPolicy
            ViewerProtocolPolicy: redirect-to-https
          # Optional - Default Response example
          DefaultRootObject: index.json
          ## Optional - Any 403, and 404's will go directly to /404.json (just an example)
            - ErrorCode: 403
              ResponseCode: 200 
              ResponsePagePath: /404.json 
            - ErrorCode: 404
              ResponseCode: 200 # friendly response in this example
              ResponsePagePath: /404.json 

          HttpVersion: http2

  # optional 2 - Route53 Auto Assign subdomain - Comment out the below if you do not want Route53 to update DNS and add a record for your new subdomain        
      Type: AWS::Route53::RecordSet
        HostedZoneName: ${env:MY_ROOT_DOMAIN}.
        Name: ${env:MY_DOMAIN_ALIAS}.
        Type: A
          DNSName: !GetAtt MyCloudFrontDistribution.DomainName
          HostedZoneId: Z2FDTNDATAQYW2
      Type: AWS::Route53::RecordSet
        HostedZoneName: ${env:MY_ROOT_DOMAIN}.
        Name: ${env:MY_DOMAIN_ALIAS}.
        Type: AAAA
          DNSName: !GetAtt MyCloudFrontDistribution.DomainName
          HostedZoneId: Z2FDTNDATAQYW2

  ## In order to print out the hosted domain via `serverless info` we need to define the DomainName output for CloudFormation
      Value: "Fn::GetAtt": [MyCloudFrontDistribution, DomainName]

Optional - Read back outputs from CLI:

This is optional but allows us to get back the CloudFront domain name

aws cloudformation --profile IAM_PROFILE  --region REGION describe-stacks --stack-name SERVICE_NAME-STAGE --query "Stacks[0].Outputs"

Uploading files to the S3 Bucket

You can either upload your testing files manually, or you can use the AWS CLI to upload them for you like this:

aws s3 cp ./test-files/ s3://example-private-bucket/ --recursive --profile default --region us-east-1

Testing our files:

	.then(response => response.text())
	.then(data => console.log(data));

CORS Errors

If you ALLOWED_ORIGINS is set to * then any domain should be able to access the test files. Howver if you've set a value, then you'll need to be testing from the allowed domains.

Adding multiple origins:

To do this you will need to edit the serverless file directly and add other domains?

			- AllowedOrigins:
					- ${env:ALLOWED_ORIGINS}

Can I add localhost to the allowed origins?

Yes you can add localhost to the allowed origins, but you will need to make sure that you are using the correct port number. For example if you are running a local server on port 3000 then you will need to add http://localhost:3000 to the allowed origins.

			- AllowedOrigins:
					- ${env:ALLOWED_ORIGINS}
					- http://localhost:3000

Adding CloudFlare

If you have a domain being managed in CloudFlare instead of Route 53, then to spin this up you want to do the following:

  1. You will still need a certificate in ACM for your domain and subdomain, so grab this ARN (this is required for the domain Alias still even if using CloudFlares SSL proxy) and set the MY_ACM_CERTIFICATE_ARN
  2. Comment out the Route53RecordIPv4 and Route53RecordIPv6 sections in the serverless.yml file
  3. Also comment out MY_ROOT_DOMAIN in the .env file
  4. Deploy the stack and note the CloudFront Distribution domain name (you can get this via the CLI command above) - it looks like
  5. Open CloudFlare and add a CNAME record for the subdomain you specified in the .env file and point it to the CloudFront Distribution domain name