AWS – Hosting a static website

How to Create a web API with ASP.NET Core 3.1

It’s really easy to host a static website on AWS. It turns out that it’s also dirt cheap, fast, reliable, and massively scalable.

You do this by storing your content in an S3 bucket and configuring that bucket to behave like a website.

It’s important to note that we’re talking about static content only. This method doesn’t work for websites requiring server-side processing or some other backend functionality. WordPress, for example, requires a hypertext preprocessor (PHP), which means you need a fully functional web server to run it. S3 won’t interpret PHP pages for you; it will just serve files straight to the browser.

So, why would you want to host a static website in S3? Common scenarios we encounter include the following:

  • Simply, your website is completely static and you don’t change it very often.
  • Your company is launching a new product or service. You’re expecting very large numbers of customers to visit a mini-site within a short time period, which is likely to be more traffic than your existing web-hosting environment can handle.
  • You need somewhere to host a failover or down-for-maintenance style page that is separate from your existing web-hosting environment:
HTTPS is not supported by S3 when it is used to serve static content. If you require HTTPS, configure CloudFront along with Amazon Certificate Manager.
Serving static content from S3

How to do it…

This recipe provides you with CloudFormation, which is necessary to create the following:

  • An S3 bucket for hosting your content
  • A Route 53-hosted zone and necessary DNS records
  • A redirection from www to root/apex for your domain

After running CloudFormation, you will, of course, need to upload your content to the buckets that CloudFormation created for you.

Creating S3 buckets and hosting content

In this example, we’re actually going to create two S3 buckets for our site ( http://www.example.org/ ). They correspond to www.example.org and example.org hostnames:

  1. We’re going to put all our content in our  example.org bucket and tell S3 that requests to  www.example.org should be redirected to the other bucket. Here’s what the relevant parts of CloudFormation would look like for creating these buckets (note that we’ll be expanding on this example as we proceed through this recipe). Create a new CloudFormation template file called 03-01-Website.yaml and enter the following code:
Resources: 
  ApexBucket: 
    Type: AWS::S3::Bucket 
    Properties: 
      BucketName: !Ref DomainName 
  WWWBucket: 
    Type: AWS::S3::Bucket 
    Properties: 
      BucketName: !Sub 
        - www.${Domain} 
        - Domain: !Ref DomainName
This might be a good time to remind you that S3 bucket names are globally unique. You’ll need to replace  example.org with a domain that you own.
  1. We won’t be hardcoding our domain name into the bucket names. Instead, we’re going to supply our domain as a parameter to the CloudFormation template in order to maximize its reusability, and then reference it via !Ref DomainName. To keep this recipe as simple as possible, we’re going to set up a single-page website. In the real world, your website will, of course, consist of multiple files, but the process you need to follow is exactly the same.
  1. Now, we need to configure the index document:
    • The index document is the file that S3 will serve by default when someone types your domain name into the address bar in their browser. This precludes the user from having to type the full path to a file, that is, example.org/index.html.
    • Typically, your index document will be called index.html. We’ll provide a code snippet for this file toward the end of this chapter.
  1. Next, we need to configure the error document:
    • The error document is the file S3 will serve if something goes wrong (missing files, forbidden access, bad requests, and so on). To keep things consistent, we’re going to call ours error.html. Again, we’ll provide a code snippet for this later in this chapter.
  2. Now, we need to enable website hosting on our bucket:
    • As we mentioned previously, we’re going to need to tell S3 that it should serve static website content from our  example.org bucket. Often, users will perform this configuration through the S3 web console. We’re going to do it in CloudFormation, however. The CLI also offers a nice one-liner for doing this. You’re not going to need to run this command; we’re just adding it here for reference:
aws s3 website s3://example.org/ \
     --index-document index.html --error-document error.html
  1. Next, we will set up a redirection from the www hostname:
    • When performing this task manually, you have little option but to fire up the web console and configure the  www.example.org bucket to redirect to the  example.org bucket. There’s no handy one-line CLI command for this one. Fortunately, it’s easy in CloudFormation, as you’ll soon see in the upcoming CloudFormation snippet.
  1. Let’s configure some permissions:
    • The last bucket setup task is to configure permissions. By default, S3 buckets are private, and only the bucket owner can see its contents. This is not much use to us in this scenario because we need everyone to be able to see our bucket’s contents. This is a public website, after all.
  1. If we were configuring our bucket manually, we would apply a bucket policy, which looks something like this:
      { 
        "Version":"2012-10-17", 
        "Statement": [{ 
          "Sid": "Allow Public Access to everything in our bucket", 
          "Effect": "Allow", 
          "Principal": "*", 
          "Action": "s3:GetObject", 
          "Resource": "arn:aws:s3:::example.org/*" 
        } 
       ] 
      }
  1. Fortunately, in CloudFormation, the task is much simpler. Building on the previous example, the Resources section of our CloudFormation template now looks like this:
Resources: 
  ApexBucket: 
    Type: AWS::S3::Bucket 
    Properties: 
      BucketName: 
        Ref: DomainName 
      AccessControl: PublicRead 
      WebsiteConfiguration: 
        IndexDocument: index.html 
        ErrorDocument: error.html 
  WWWBucket: 
    Type: AWS::S3::Bucket 
    Properties: 
      BucketName: 
        Fn::Join: [ ., [ www, Ref: DomainName ] ]
      AccessControl: BucketOwnerFullControl 
      WebsiteConfiguration: 
        RedirectAllRequestsTo: 
          HostName: 
            Ref: ApexBucket

We still have more changes to make to the file, as described in the following section.

Creating a hosted zone

To start adding DNS records, we need to add a hosted zone to Route 53. As you can see in the following code, this is reasonably simple to do. The Name we are going to supply will be provided as a parameter to our CloudFormation template. Add the following code to the top of the file:

Parameters:
  DNSHostedZone: 
    Type: "AWS::Route53::HostedZone" 
    Properties: 
      Name: 
        Ref: DomainName

Creating DNS records

Follow these steps to create your DNS records:

  1. Now that we have a hosted zone, we can go ahead and create DNS records for it. For this, we use the AWS resource type known as AWS::Route53::RecordSetGroup.
  2. We’re going to create an A record for our domain’s root/apex entry, and we’ll make it an alias. This alias will be configured to point to the AWS endpoint for S3-hosted websites in the particular region we choose to run this CloudFormation in.
  3. To achieve region portability in our template, we’ll use a mapping to provide all the endpoints. The values in this map are published by AWS in their API endpoints documentation. You won’t need to look these up, however, because our code sample provides the most up-to-date endpoints (at the time of writing). The endpoints tend not to change, but the list obviously grows when AWS adds more regions.
  4. The mapping will look like this:
        us-east-1: 
          S3HostedZoneID: Z3AQBSTGFYJSTF 
          S3AliasTarget: s3-website-us-east-1.amazonaws.com 
        us-east-2: 
          S3HostedZoneID: Z2O1EMRO9K5GLX 
          S3AliasTarget: s3-website.us-east-2.amazonaws.com

We’ll also need a CNAME for wwwwhich will point at our WWWBucket so that redirection can take place. The final resource for our DNS records will look like this:

        DNSRecords: 
          Type: "AWS::Route53::RecordSetGroup" 
          Properties: 
            HostedZoneId: 
              Ref: DNSHostedZone 
            RecordSets: 
              - Name: 
                  Ref: DomainName 
                Type: A 
                AliasTarget: 
                  HostedZoneId: 
                    Fn::FindInMap: [ RegionMap, Ref: "AWS::Region",
                      S3HostedZoneID ]
                  DNSName: 
                    Fn::FindInMap: [ RegionMap, Ref: "AWS::Region",
                      S3AliasTarget ]
              - Name: 
                  Fn::Join: [ ., [ www, Ref: DomainName ] ] 
                Type: CNAME 
                TTL: 300 
                ResourceRecords: 
                  - Fn::GetAtt: WWWBucket.DomainName
  1. Now, we’re ready for launch. It’s time to create our CloudFormation stack. You can download the complete template file from the GitHub repository for this book. Create the stack using the following CLI command:
      aws cloudformation create-stack \ 
        --stack-name static-website-1 \ 
        --template-body file://03-01-Website.yaml \ 
        --parameters \
        ParameterKey=DomainName,ParameterValue=<your-domain-name>

Once the stack completes, you will be able to add some content to your new website.

Uploading website content

Now, it’s time to upload some content to our S3 buckets. Here are the snippets we promised you earlier. There’s nothing fancy here. Once you’ve got these examples working, you can try replacing them with your real website content.

Save this content as index.html:

      <html> 
        <head> 
          <title>Welcome to example.org</title> 
        </head> 
        <body> 
          <h1>example.org</h1> 
          <p>Hello World!</p> 
        </body> 
      </html>

Save this content as error.html:

      <html> 
        <head> 
          <title>Error</title> 
        </head> 
        <body> 
          <h1>example.org</h1> 
          <p>Something went wrong!</p> 
        </body> 
      </html>

How it works…

That’s it! As soon as S3 has an index.html file to serve up, you will be hosting a single-page website on S3. Go ahead and test it out. The CloudFormation example that’s supplied will output a URL you can use to see your new website. After you’ve verified it’s working, you can go ahead and upload your real static website and enjoy fast, cheap, and server-free hosting.

There’s more…

Let’s look at some additional things to consider.

Delegating your domain to AWS

While we’ve created a hosted zone and some DNS records in Route 53, no one can actually see them yet. To send your website visitors to your new S3 static website, you’ll need to delegate your domain to Route 53. This is left to you as an exercise; however, there are some important things to remember:

  • The DNS servers to delegate your domain to can be found in the NS record for your hosted zone.
  • If your domain is already live and production-like, you’ll need to make sure all your DNS records for your zone are recreated in Route 53, including things such as MX records, which are critical for the continuity of your email service.
  • Before delegating to AWS, you may consider reducing the time to live (TTL) values on your DNS records. This will be useful if, for some reason, you need to redelegate or make changes to them. Once your DNS setup is stable, you can increase TTLs.

Cross-origin resource sharing (CORS)

It’s worth discussing CORS here because the more static web content hosting you do in S3, the higher your chances are of needing to know about this, particularly where web fonts are concerned.

Some browsers implement a same-origin policy restriction. This prevents the browser from loading certain kinds of assets from hostnames that are different than the page being displayed to the user. Web fonts fall under this restriction and are an often-cited example, because, when they don’t load correctly, your website will usually look a lot different than how you intended. The solution to this is to add a CORS configuration to your bucket to allow its content to be loaded by the particular origin or hostname that requested it.

We’ll leave the CORS configuration out of our full example, but if you need to add one to your bucket, here’s how you can do it. Update your AllowedOrigins property to look similar to the following CloudFormation, and you should be all set:

  ApexBucket: 
    Type: AWS::S3::Bucket 
    Properties: 
      BucketName: !Ref DomainName 
      AccessControl: PublicRead 
      WebsiteConfiguration: 
        IndexDocument: index.html 
        ErrorDocument: error.html 
      CorsConfiguration: 
        CorsRules: 
        - AllowedOrigins: 
            - example.net 
            - www.example.net 
            - example.com 
            - www.example.com 
          AllowedMethods: 
            - GET 
          MaxAge: 3000 
          AllowedHeaders: 
            - Content-* 
            - Host

See also

  • In this section, we covered how to simply and easily convert a public bucket into a website. But wait  aren’t we always hearing about how it’s bad practice to make an S3 bucket public? While a static website is one of the few legitimate use cases for a public bucket, there is a better way. See the next recipe, Caching a website with CloudFront, to learn how to securely host the contents of a private bucket using Amazon CloudFront.

Comments are closed.