DEV Community

Cover image for AWS project - Module 3. Use Your Custom Domain for Static Website Hosted on AWS S3 via Route 53 and CloudFormation
Samira Yusifova
Samira Yusifova

Posted on

AWS project - Module 3. Use Your Custom Domain for Static Website Hosted on AWS S3 via Route 53 and CloudFormation

Overview

In Module 1, we have created a simple static website and hosted it on S3 bucket. To access our website we used Amazon S3 website endpoint which looked like http://example.s3-website-us-east-1.amazonaws.com. However for users the link is too long and hard to remember. If you want your website appear legitimate to visitors you should use a custom domain name such as http://example.com.

In this module I'll show you how to configure Route 53 to use your domain to access the website on S3 bucket in six simple steps via CloudFormation:
✳️ Step 1. Register a custom domain name
✳️ Step 2: Create an S3 bucket for your root domain and (optionally) for subdomain
✳️ Step 3. Build and deploy static files to S3 bucket automatically using CodeBuild
✳️ Step 4. Create Route 53 hosted zone and configure your domain
✳️ Step 5. Create Route 53 record set to route your traffic for your domain to S3 bucket
✳️ Step 6. Test your static website

Architecture

The high-level architecture for our project is illustrated in the diagram below:

Image description

In this module we will focus on configuring S3 buckets and Route 53 records so that:
✳️ your users will be able to use domain name (such as example.com) to access your static website hosted on S3 bucket for root domain

Image description

✳️ (optionally) your users will be able to use subdomain (such as www.example.com) to access your static website hosted on S3 bucket for root domain by redirecting here from S3 bucket for subdomain

Image description

Source code

Source code for this project is available on my GitHub profile in a public repository named StaticWebsiteHostingToS3 (check it here)
👉 Frontend source code link
👉 Module 3 CloudFormation templates link

List of CloudFormation templates:
1️⃣ Template for S3 buckets link
2️⃣ Template for CodeBuild project link
3️⃣ Template for Route 53 hosted zone link
4️⃣ Template for Route 53 record set here

Initial Setup

Create required accounts:
👉 AWS account
👉 GitHub account

Install required tools locally:
👉 any IDE (personally I prefer Visual Studio Code) or any text editor
👉 git
❎ no need to install any package locally for our Angular project

AWS Resources

Here is the list of AWS resources that we are going to provision with CloudFormation:
👉 S3 bucket(s)
👉 S3 bucket policy
👉 CodeBuild project
👉 IAM role for CodeBuild project
👉 GitHub Source Credential for CodeBuild project
👉 Route53 hosted zone
👉 Route53 record set group(s)

Why use domain name?

Why should you get a custom domain? Let's break it down!

☑️ Brand Superpowers: A custom domain name represents your brand and helps people easily recognize and remember you. Keep it consistent and unleash your brand's power.

☑️ Memorable Magic: The secret to success is having a domain name that sticks in people's minds like a catchy tune. The easier it is to remember, the more people will visit your site.

☑️ Credibility Boost: With a custom domain, your website becomes the ultimate credibility booster. It shows visitors that you mean business and adds a professional touch.

☑️ Authority Quest: As your domain ages, it gains super search engine authority! By creating quality content and building links, you'll level up your domain's power over time.

☑️ Email Address: The bonus perk of having a custom domain — your very own personalized email address!

☑️ Portability: With a custom domain, you can seamlessly move to different web hosting providers or website platforms while keeping the same web address. It's the ultimate flexibility that lets you adapt and expand your online presence without losing your trusted brand identity.

Image description

Few things you should consider when coming up with a domain name:
👉 Keep it short and sweet
👉 Make it unique
👉 Use easy to spell and pronounce name

Step 1. Register a custom domain name

So you've decided to pay for a custom domain name. Congratulations on taking a significant first step towards establishing search authority, credibility, and trust!

To use a domain name (such as example.com), you must find a domain name that isn't already in use and register it. When you register a domain name, you reserve it for your exclusive use everywhere on the internet, typically for one year. (from AWS Developer Guide)

Buying a new domain generally costs between $10 and $20 a year. Price differences depend on which registrar you buy your domain name from, and what kind of domain you're buying. Different registrars offer different packages, so it's worth shopping around to find your best fit.

📌 Note: you can register a new domain with AWS, Google Domains, NameCheap, GoDaddy, HostGator, or any other registrar. Here is the list of Best Domain Registrar 2023 according to Forbes.

Once you choose a domain registrar, all you need is simply to follow 3 general steps:

✳️ Step 1. Choose a unique domain name
✳️ Step 2. Choose a domain name registrar
✳️ Step 3. Purchase and register your domain name

Follow How To Register A Domain Name (2023 Guide) link for general instructions.

Here are the links to some registrars:

👉 AWS using Amazon Route 53 link
👉 Google Domains link (my personal choice, I'm paying $12 annually)
👉 GoDaddy link
👉 HostGator link
👉 NameCheap link

Image description

Step 2: Create an S3 bucket for your root domain and (optionally) for subdomain

Let's take a step back to quickly recap domain name structure.

Image description

Domain names have a specific structure that consists of several parts. Let's break it down:

1️⃣ Top-Level Domain (TLD): This is the rightmost part of a domain name and represents the highest level in the domain name system hierarchy. Common examples include .com, .org, .net, .edu, .gov, etc.

2️⃣ Second-Level Domain (SLD): The SLD is the part that comes before the TLD and is the main identifier of your website or brand. It typically represents the name of your organization, business, or the purpose of your website.

3️⃣ Subdomain: A subdomain is an optional part that appears before the SLD. It allows you to organize and categorize different sections or services of your website. For example, in blog.example.com, "blog" is a subdomain.

4️⃣ Third-Level Domain and beyond: It is possible to have additional levels of subdomains. For instance, support.blog.example.com has two levels of subdomains ("support" and "blog") before the SLD ("example") and the TLD (".com").

📌 Note: A domain name isn’t the same thing as a uniform resource locator (URL). A URL is the full web address of a site, and while it does contain the domain name, it contains other information, too. Each URL includes the internet protocol (most commonly HTTP or HTTPS) being used to call up the page. URLs can also help point browsers to a specific file or folder being hosted on a web server (for example https://www.my-site.com/blog?page=1).

Image description

Now enough theory, let's talk about implementation!

In Module 1 we have discussed how to provision S3 bucket to host our static website. To configure it with Route 53 we need to make some adjustments.

📌 When you configure an Amazon S3 bucket for website hosting, you must give the bucket the same name as the record that you want to use to route traffic to the bucket. For example, if you want to route traffic for example.com to an S3 bucket that is configured for website hosting, the name of the bucket must be example.com.

Once you registered a new domain name, we can use it to provision an S3 bucket with the same name.

Get the full CloudFormation template for S3 buckets from here.

1️⃣ The following piece of code helps to create a new S3 bucket and configure it to host a static website:

Resources:
  myS3BucketForRootDomain:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Retain # keep S3 bucket when its stack is deleted
    Properties:
      BucketName: my-domain-name # use the name of your domain, such as example.com
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: error.html
      VersioningConfiguration: # turn versioning on in case we need to rollback newly built files to older version
        Status: Enabled
      # AccessControl: PublicRead # throws an error: Bucket cannot have public ACLs set with BlockPublicAccess enabled
      OwnershipControls:
        Rules:
          - ObjectOwnership: ObjectWriter
      PublicAccessBlockConfiguration:
        BlockPublicAcls: false
        BlockPublicPolicy: false
        IgnorePublicAcls: false
        RestrictPublicBuckets: false
Enter fullscreen mode Exit fullscreen mode

This S3 bucket for root domain will contain our static website files. Don't forget to replace my-domain-name with your domain name (such as example.com).

We will push static files to S3 bucket for root domain with the help of CodeBuild in Step 3.

Once we configure Route 53 in Step 4 and Step 5, your users will be able to use my-domain-name (such as example.com) to access your static website hosted on S3 bucket for root domain.

2️⃣ If you also want your users to be able to use subdomain (such as www.example.com), to access your static website, create a second S3 bucket.

📌 Our second (subdomain) bucket will not host a static website. We will keep it empty and configure it to route traffic to the first bucket.

The following piece of code helps to create the second S3 bucket to redirect the traffic to the first bucket:

Resources:
  myS3BucketForSubdomain:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Retain # keep S3 bucket when its stack is deleted
    Properties:
      BucketName:  my-subdomain.my-domain-name # use the name of subdomain with domain, such as www.example.com
      WebsiteConfiguration:
        RedirectAllRequestsTo: # Configure the bucket to route traffic to the S3 bucket for root domain
          HostName: !Ref myS3BucketForRootDomain
          Protocol: http
      AccessControl: BucketOwnerFullControl
Enter fullscreen mode Exit fullscreen mode

📌 Note: RedirectAllRequestsTo property of S3 bucket helps to configure second (subdomain) bucket to route traffic to the first (root domain) bucket.

You can use CloudFormation Condition function to provision second (subdomain) bucket only if a subdomain was specified as a parameter:

Parameters:
  paramSubdomain:
    Description: OPTIONAL. Specify a subdomain (such as 'www' or 'apex' for www.example.com or apex.example.com). You can leave it empty to skip.
    Type: String
    Default: www

Conditions:
  # HasSubdomainName is false if paramSubdomain value is empty string
  HasSubdomainName: !Not [!Equals [!Ref paramSubdomain, '']] 

Resources:
 myS3BucketForSubdomain:
    Condition: HasSubdomainName # skip this resource if paramSubdomain value is empty string
    Type: 'AWS::S3::Bucket'

Enter fullscreen mode Exit fullscreen mode

3️⃣ The next step is to create a bucket policy for the first (root domain) bucket.

We need to give a public read access to S3 bucket objects. Here is the policy:

Resources:
  myPolicyForS3BucketForRootDomain:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref myS3BucketForRootDomain
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: PublicReadForGetBucketObjects
            Effect: Allow
            Principal: '*'
            Action: 's3:GetObject'
            Resource: !Sub "${myS3BucketForRootDomain.Arn}/*"
Enter fullscreen mode Exit fullscreen mode

4️⃣ We are done with Resources, now let's output Amazon S3 website endpoint for both root domain and subdomain to make it easier to navigate using the link once the stack is built.

Outputs:
  outputS3WebsiteURLForRootDomain:
    Description: Amazon S3 website endpoint for root domain
    Value: !GetAtt myS3BucketForRootDomain.WebsiteURL
  outputS3WebsiteURLForSubomain:
    Condition: HasSubdomainName
    Description: Amazon S3 website endpoint for subdomain
    Value: !GetAtt myS3BucketForSubdomain.WebsiteURL
Enter fullscreen mode Exit fullscreen mode

5️⃣ We are ready to create and run CloudFormation stack based on our template.

If you need to understand how to create a stack in AWS Console, please read Hands-on AWS CloudFormation - Part 1. It All Starts Here.

Upload our template file for S3 buckets (link) to AWS CloudFormation to create a stack. Specify your domain name and subdomain as parameters values. (I'm going to use domain name I've already registered, which is tiamatt.com.) Run the stack.

Image description

Once the stack built, you should see two newly created buckets and the policy under Resources tab:

Image description

Under Outputs tab you can find two URLs to our website - for domain and subdomain:

Image description

Currently, if you navigate to http://my-domain-name.com.s3-website-us-east-1.amazonaws.com using Amazon S3 website endpoint for root domain then you should get 404 error as we haven't deployed static content to S3 bucket yet.

Image description

To resolve this issue we need to deploy the static content to our S3 bucket for root domain (see Step 3).

If you navigate to http://www.my-domain-name.com.s3-website-us-east-1.amazonaws.com using Amazon S3 website endpoint for subdomain, then your link will be changed to http://my-domain-name.com with This site can’t be reached error. To resolve this issue we need to configure Route 53 (see Step 4 and Step 5).

Step 3. Build and deploy static files to S3 bucket automatically using CodeBuild

In Module 2 we have discussed how to automate the build of a static website on AWS S3 via CodeBuild step by step. Please follow the guide to provision CodeBuild project and run CloudFormation stack for CodeBuild.

Get the full CloudFormation template for CodeBuild project from here.

📌 Note: you don't need to know anything about Angular or install NodeJS, NPM and Angular CLI locally. CodeBuild project will automate the build of Angular project and safe built files for a static website on S3 bucket for root domain.

1️⃣ When you run the stack, change the value of paramS3BucketNameForRootDomain input parameter to name that you used building a stack for S3 bucket for root domain (such as example.com). Then run the stack. And don't forget to pass your GitHub access token as value of paramPersonalGitHubAccessToken input parameter.

Image description

Once the stack built, you should see a newly created CodeBuild project and its role under Resources tab:

Image description

You can find our newly create CodeBuild project under CodeBuild -> Build projects:

Image description

2️⃣ Update frontend source code and watch how it will be built automatically.

Open any IDE or notepad on your machine and make some changes in frontend\app-for-aws\src\app\app.component.html file (it's totally up to you). I want to change the title to

<!-- Resources -->
<h2>Good news, everyone! Testing Module 3!</h2>
Enter fullscreen mode Exit fullscreen mode

Image description

Push the changes to remote repo. Then navigate to our CodeBuild project on AWS Console, you should see that build was triggered automatically and its status is in progress. Wait till the status has changed to succeeded:

Image description

Go to the browser and use Amazon S3 website endpoint for root domain to navigate to the website:

Image description

📌 Note: Amazon S3 website endpoint for subdomain will not work as we haven't configured Route 53 yet (see Step 4 and Step 5).

Step 4. Create Route 53 hosted zone and configure your domain

📌 Note: When you register your domain using AWS registrar, Route 53 automatically creates a hosted zone with the same name. Skip this step if you have already registered your domain with AWS.

Let's take a step back to quickly recap what Route 53, hosted zones and records are.

Route 53 is Domain Name System (DNS) web service provided by AWS team. It offers domain registration, DNS routing, and health checking services (from AWS FAQs):

👉 With Route 53, you can create and manage your public DNS records. Like a phone book, Route 53 lets you manage the IP addresses listed for your domain names in the Internet’s DNS phone book.

👉 Route 53 also answers requests to translate specific domain names like into their corresponding IP addresses like 192.0.2.1. You can use Route 53 to create DNS records for a new domain or transfer DNS records for an existing domain. The simple, standards-based REST API for Route 53 allows you to easily create, update and manage DNS records.

👉 Route 53 additionally offers health checks to monitor the health and performance of your application as well as your web servers and other resources. You can also register new domain names or transfer in existing domain names to be managed by Route 53.

Image description

In our case we need to configure DNS routing to transfer internet traffic from our domain (such as example.com) to Amazon S3 website endpoint (such as http://example.s3-website-us-east-1.amazonaws.com) to access the website on S3.

In order to configure our domain name to point to Amazon S3 website endpoint, we need to create a public hosted zone and add routing records there.

Hosted zone is an Amazon Route 53 concept. A hosted zone is analogous to a traditional DNS zone file; it represents a collection of records that can be managed together, belonging to a single parent domain name. All resource record sets within a hosted zone must have the hosted zone's domain name as a suffix. For example, the example.com hosted zone may contain records named www.example.com, and www.blog.example.com, but not a record named www.amazon.com (from AWS FAQs).

Each record contains information about how you want Route 53 to route traffic for a specific domain, and its subdomains. For example, you can specify a record to route your traffic for example.com to S3 bucket, or API Gateway, or Load Balancer, or another AWS resources.

Get the full CloudFormation template for Route 53 hosted zone from here

1️⃣ The following piece of code helps to create a public hosted zone:

Resources:
  myRoute53HostedZone:
    Type: 'AWS::Route53::HostedZone'
    Properties:
      HostedZoneConfig: 
        Comment: My public hosted zone for example.com
      Name: example.com
Enter fullscreen mode Exit fullscreen mode

📌 Note: A hosted zone and the corresponding domain should have the same name.

2️⃣ Let's output hosted zone ID that we will use for Route 53 record set configuration. Also we can get the list of all name servers (NS) where the traffic will be routed to:

Outputs:
  outputRoute53HostedZoneId:
    Description: Public hosted zone ID (such as Z23ABC4XYZL05B)
    Value: !GetAtt myRoute53HostedZone.Id # the same as !Ref myRoute53HostedZone  
  outputRoute53HostedZoneNameServers:
    Description: List of name servers for newly created public hosted zone
    Value: !Join [', ', !GetAtt myRoute53HostedZone.NameServers] 
Enter fullscreen mode Exit fullscreen mode

3️⃣ Upload our template file for hosted zone (link) to AWS CloudFormation to create a stack.

Once the stack built, a new public hosted zone will be provisioned.

Under Resources tab you can find the link to hosted zone:

Image description

Under Outputs tab you can find the hosted zone ID for our domain and the list of all name servers (NS):

Image description

If you navigate to Route 53 -> Hosted zones, you will see your newly created hosted zone (such as example.com) with two records of type NS and SOA:

Image description

Both a name server (NS) record and a start of authority (SOA) record are created by Route 53 automatically while provisioning a new public hosted zone. You rarely need to change these records.

✳️ Name server (NS) record lists the four name servers that are the authoritative name servers for your hosted zone.

✳️ Start of authority (SOA) record identifies the base DNS information about the domain.

📌 Note: When you register your domain outside of AWS Route 53, you need to connect your DNS to Route 53.

As I registered my domain on Google Domains, I'll show you how I connected my Google domain to Route 53. Similar steps can be applied for other registrar platforms.

1️⃣ First, go to your Route 53 hosted zone and copy all four NS records from there:

Image description

2️⃣ Now, log into your Google Domains account. Then click on My domains -> select DNS from menu -> click on Custom name servers (Active) tab. Click on Manage name servers.

Image description

Click on Add another name server and paste four NS records from the Route 53 Record Sets panel one by one. Don't forget to Save changes:

Image description

Ta-da! Now our domain has been connected to Route 53.

Step 5. Create Route 53 record set to route your traffic for your domain to S3 bucket

After you create a hosted zone for your domain, such as example.com, you need to create records to tell the Domain Name System (DNS) how you want traffic to be routed for that domain.

Get the full CloudFormation template for Route 53 record set from here

1️⃣ In our case we need to create a record to route internet traffic from root domain (such as example.com) to S3 bucket that hosts static website.

Image description

The following piece of code helps to create a new record for S3 bucket for root domain:

Resources:
  myRoute53RecordSetGroupForRootDomain:
    Type: 'AWS::Route53::RecordSetGroup'
    Properties:
      HostedZoneId: !Ref paramHostedZoneId
      RecordSets:
        - Name: example.com # point to an S3 bucket with root domain in the same account (such as example.com)
          Type: A # 'A' routes traffic to an IPv4 address and some AWS resources
          AliasTarget:
              DNSName: !Sub s3-website-${AWS::Region}.amazonaws.com
              HostedZoneId: !FindInMap # note, that it is different from paramHostedZoneId - this hosted zone is for region that you created the bucket in!
                - RegionMap
                - !Ref 'AWS::Region'
                - S3HostedZoneId
Enter fullscreen mode Exit fullscreen mode

📌 Note: HostedZoneId specified under AliasTarget is different from hosted zone id that we have created in Route 53 for our domain in Step 4. Hosted zone id for S3 is a magical alphanumeric ID provided by AWS team. It varies base on the region that you created the bucket in. That's why we used mapping to map S3 hosted zone id to region:

Mappings:
  RegionMap: # based on https://docs.aws.amazon.com/general/latest/gr/s3.html#s3_website_region_endpoints
    us-east-1:
      S3HostedZoneId: Z3AQBSTGFYJSTF
    us-west-1:
      S3HostedZoneId: Z2F56UZL2M1ACD
    us-west-2:
      S3HostedZoneId: Z3BJ6K6RIION7M
    eu-central-1:
      S3HostedZoneId: Z21DNDUVLTQW6Q
    eu-west-1:
      S3HostedZoneId: Z1BKCTXD74EZPE
    ap-southeast-1:
      S3HostedZoneId: Z3O0J2DXBE1FTB
    ap-southeast-2:
      S3HostedZoneId: Z1WCIGYICN2BYD
    ap-northeast-1:
      S3HostedZoneId: Z2M4EHUR26P7ZW
    sa-east-1:
      S3HostedZoneId: Z31GFT0UA1I2HV
Enter fullscreen mode Exit fullscreen mode

See the whole list of hosted zone ids for S3 here.

📌 Also note for future reference that hosted zone id depends on your alias target. For example:

  • for Global Accelerator accelerator specify Z2BJ6XQ5FK7U4H
  • for CloudFront distribution specify Z2FDTNDATAQYW2
  • etc

See the whole list of hosted zone ids for different AWS services here.

2️⃣ Optionally, we can create a new record for S3 subdomain bucket which will redirect traffic to S3 bucket for root domain.

Image description

The following piece of code helps to create a new record for S3 bucket for subdomain:

Resources:
  myRoute53RecordSetGroupForSubdomain:
    Condition: HasSubdomainName # skip this resource if paramSubdomain value is empty string
    Type: 'AWS::Route53::RecordSetGroup'
    Properties:
      HostedZoneId: !Ref paramHostedZoneId
      RecordSets:
        - Name: www.example.com # point to an S3 bucket with subdomain in the same account (such as www.example.com)
          Type: A # 'A' routes traffic to an IPv4 address and some AWS resources
          AliasTarget:
              DNSName: !Sub s3-website-${AWS::Region}.amazonaws.com 
              HostedZoneId: !FindInMap # note, that it is different from paramHostedZoneId - this hosted zone is for region that you created the bucket in!
                - RegionMap
                - !Ref 'AWS::Region'
                - S3HostedZoneId  
Enter fullscreen mode Exit fullscreen mode

3️⃣ Upload our template file for Route 53 record sets (link) to AWS CloudFormation to create a stack.

Once the stack built, you should see two newly created Route 53 record set groups under Resources tab:

Image description

Under Outputs tab you can find two URLs to our website - with domain name and subdomain:

Image description

Now we are done with provisioning of AWS resources. Our infrastructure is ready. Let's test our website.

Step 6. Test your static website

To verify that the website is working correctly, open a web browser and browse to http://my-domain-name (such as example.com). I'm using my custom domain name I've already registered, which is tiamatt.com:

Image description

Now, let's check our subdomain. Browse to http://your-subdomain.my-domain-name (such as www.example.com). You should see that it will redirect your request to your http://my-domain-name domain (such as example.com).

Cleanup

You might be charged for running resources. That is why it is important to clean all provisioned resources once you are done with the stack. By deleting a stack, all its resources will be deleted as well.

📌 Note: CloudFormation won’t delete an S3 bucket that contains objects. First make sure to empty the bucket before deleting the stack.

Image description

Summary

Well done on reaching this point!

In this module we explained how to configure Route 53 to use a custom domain for your S3-hosted static website. Say goodbye to long, hard-to-remember links! In six simple steps, we registered the domain, set up S3 buckets, deployed files with CodeBuild, configured Route 53, and tested website.

By using a custom domain, we can boost professionalism and make our site more accessible.

Top comments (0)