The complete reference guide to basic website hosting on AWS
Simple re:Directs, S3+CloudFront, API-Gateway Essentials with best practices for Security and Ready-to-Deploy CloudFormation templates
This post is meant as a reference guide and a starting place for deploying basic websites quickly and securely.
Average Read Time: 18min
Manifest
Do you want to just look at the code? Then go to https://github.com/jeeshofone/Public-AWS-Website-Patterns/tree/main)
Introduction
I love building stupid websites. I also love using AWS for deploying them. There are a lot of different way too many ways mechanisms to host a basic website on AWS. From the absurdly complicated reference architecture for Wordpress to a public S3 bucket and everything in between.
![](https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F724b8179-0fda-43a9-932e-91269329192b.heic)
To introduce myself for the new readers - my name is Will Laws - I’m a Solutions Architect at AWS currently located in Sydney Australia, and I’ve been building on AWS since 2009 (just before the launch of RDS). While I work at AWS now, the contents of my social media are my own thoughts and represent my own experience.
These are their stories
dun dun
In this post I’m going to provide a comprehensive set of patterns (and provide working, out of the box deployable CloudFormation templates) that I use everyday to make websites secure, cheap and fast.
Root Domain to Subdomain Redirects
Why would I want a root domain to a subdomain redirect? You used one getting to this blog post (if you’re reading this on 123cloud.st). Substack requires the use of a subdomain for its custom domain names. So I have to have www at the front of my URL. I don’t want you to type out the /dʌbəl.juː/ /dʌbəl.juː/ /dʌbəl.juː/ /dɒt/ so I need a way to direct you from 123cloud.st to www.123cloud.st.
Let’s talk quickly about what doesn’t work.
You may be familiar with CNAMES which “redirect” one domain name to another. This won’t work for us because of something very cool called SNI.
www.123cloud.st points to target.substack-custom-domains.com - in fact every website on Substack with a custom domain name points to this URL. We can dig
(groaning intensifies) deeper. What is at target.substack-custom-domains.com?
When we look at the ANSWER SECTION of our dig output, we see 2 IP addresses.
104.18.40.87 and 172.64.147.169 (although likely depending on where I am, I will get different addresses)
These IP’s are owned by Cloudflare which provide edge caching and distribution for Substack. Awesome. Now let’s look at SNI.
Server Name Indication, or SNI, is an extension of the TLS protocol. SNI allows a server to present multiple certificates on the same IP address and port number and hence allows multiple secure (HTTPS) websites (or any other service over TLS) to be served by the same IP address without requiring all those sites to use the same certificate.
If we did not have SNI, we would need a separate IP address for every single site that needs a secure connection using a unique https certificate, which is obviously not scalable with the limited number of IPv4 addresses and would be a huge waste of IPv6 addresses as well.
For example, when your browser attempts to establish a secure connection with www.123cloud.st, it includes the hostname in the TLS handshake so that the server knows which website's certificate to present. If SNI were not used, Cloudflare would have no way to know which certificate to present because it hosts possibly thousands of different domains on a single IP address. As such, SNI is crucial for a multi-tenant hosting environment like Cloudflare's edge network.
This is why when I setup “ww.123cloud.st” (I did set it up, try it!) to point at “www.123cloud.st” with a CNAME record you get an error from Cloudflare. It doesn’t know what you’re talking about as the origin subdomain is ww
and not www
.
Subdomain Redirection (Option 1) Finally he’s showing us a solution
Since we can’t use DNS to direct our users around, we need to actually use an http redirect. There are many different kinds of http redirects. Lets look at this one first
<html><head><meta http-equiv="refresh" content="0; url=https://{redirection_url}"></head></html>
The “redirect” above is technically just refreshing your browser window and loading a new page. Some SEO experts will not recommend doing this; but how about we do it anyway? Well since we’re exploring AWS Native mechanisms; how will we host this redirect?
Let me explain how the template works.
Parameters: Inputs required for the template to work.
RootDomainName
: The root domain you intend to redirect from.WWWDomainName
: The 'www' or other subdomain you want to redirect to.HostedZoneID
: The identifier of the hosted zone within Route 53 for the RootDomainName.
Resources that will be created:
RedirectBucket
: An Amazon S3 bucket configured with public access blocked that will store the redirect page.RedirectBucketPolicy
: A policy applied to the S3 bucket that allows CloudFront to access the objects in the bucket.CloudFrontOriginAccessControl
: A resource that controls access to the S3 bucket from CloudFront using Origin Access Control (OAC).CloudFrontDistributionForRedirect
: A CloudFront distribution setup to redirect the root domain to the 'www' subdomain.RedirectAcmCertificate
: An ACM certificate resource representing the SSL certificate for the root domain.Route53RecordSetGroup
: A set of Route 53 DNS records (A and AAAA Types) that point your root domain to the CloudFront distribution.LambdaExecutionRole
: An AWS Identity and Access Management (IAM) role that gives Lambda very limited permissions to interact with other AWS services.S3FileUploaderLambda
: A Lambda function that is triggered by a Custom Resource to upload the redirect HTML page to the S3 bucket.S3FileUploadCustomResource
: A custom resource that triggers the Lambda function to upload the redirect page to the S3 bucket upon stack creation.
Outputs: Named references to the values returned by the template. In this case:
CloudFrontDistributionDomainName
: The domain name of the CloudFront distribution.RedirectAcmCertificateArn
: The Amazon Resource Name (ARN) of the ACM certificate.
The overall functionality of the template is to create an S3 bucket that contains a simple HTML file that redirects our visitors to the 'www' subdomain. The bucket is made accessible only via the CloudFront distribution, which is linked to the root domain using Route 53 DNS records. An SSL/TLS certificate from ACM ensures secure browsing using HTTPS.
Upon stack creation, the Lambda function is invoked to upload the necessary HTML file into the bucket, enabling the redirection. The IAM role is provided to grant the only necessary permissions for the Lambda function to carry out its operations.
Please feel free to use this template but make sure you read and understand it before deploying it into your own account. You’ll want to deploy this in us-east-1
as CloudFront can only use ACM certificates from that region.
Link to Cloudformation template
Subdomain Redirection (Option 2)
Theoretically, a 301 redirect is the “best” - 301 is for “Moved Permanently” and many believe it is better for SEO. So how does a 301 redirect actually work?
Client Request: A user enters a URL into their browser or clicks on a link to navigate to a page.
Initial Server Response: The server that hosts the original URL receives the request for the resource (a web page, an image, etc.).
Redirect Instruction: The server checks its configuration for the requested URL. If a 301 redirect is set up, the server knows that this resource has been moved permanently to a new location. Instead of serving the original resource, the server sends back a response to the client with the status code 301, which means "Moved Permanently".
Location Header: Along with the 301 status code, the server includes a "Location" header in the response. This header specifies the new URL where the requested resource now resides.
Client Receives Redirect: The client (browser) receives the 301 status code and the new URL from the "Location" header. Understanding that the resource has been permanently moved, the browser automatically initiates a new request to the URL specified in the "Location" header.
Accessing the New Resource: The server at the new URL receives the request and, providing the resource is there, serves the desired web page or file to the client.
Updating Links: Search engines and browsers may update their links or cache to reflect the new URL. This is because a 301 redirect indicates a permanent move, and so the old URL is essentially replaced by the new one in indexes and bookmarks.
This feels a bit heavier than the simple html redirect in Option 1; however, it actually uses fewer resources but does require a public bucket.
It requires you to have a PUBLIC BUCKET due to the way that S3 redirects work with CloudFront. We can’t use an OAI or an OAC to control access via CloudFront and we must use the S3 Bucket Website endpoint which requires a public bucket. This bucket should have restrictions! However, it means you can’t put the BLOCK ALL rules on your AWS Account. This method requires this risk. I use a separate account for this use case across all my sites to prevent data from accidentally being added to these buckets.
2 important things to note when using a 301.
A client’s browser can cache the response for a period of time. These redirections should not be used for frequently changed links.
Many believe that a A 301 redirect is critical for maintaining SEO rankings. When a page is moved, the 301 redirect tells search engines that any rankings and link equity the old page had should be transferred to the new page.
So how do we do a 301 redirect with an AWS Native solution?
Link to Cloudformation template
Conditions:
CreateDNSRecordsCondition
: Determines whether DNS records will be created based on theCreateDNSRecords
parameter.UseExistingCertificateCondition
: Checks if an existing ACM Certificate ARN has been provided.CreateNewCertificateCondition
: Determines whether a new ACM certificate should be created based onCreateACMCertificate
and the presence of an existing certificate.
Resources: The AWS resources that will be created and managed by the stack.
ACMCertificateResource
: An SSL certificate managed by AWS Certificate Manager, created if needed per the conditions.S3BucketForRedirect
: A PUBLIC S3 bucket configured to redirect all incoming requests to the destination domain using HTTP 301 redirects.S3BucketPolicyForRedirect
: A bucket policy enabling public read access to the objects in the S3 bucket.CloudFrontDistribution
: A CloudFront distribution configured to sethttp-only
for the custom origin (since S3 websites do not support HTTPS) and redirect all viewer protocols to HTTPS. It uses the provided ACM certificate or the one created by the stack.Route53RecordSetGroup
: DNS records in Route 53 (A and AAAA records) pointing the source domain to the CloudFront distribution, created if DNS record creation is enabled.
Our First request to http://123cloud.st takes us on a 301 to https://123cloud.st which is served via CloudFront’s ViewerProtocolPolicy: redirect-to-https
That request (below) takes us via 301 to https://www.123cloud.st which is configured by WebsiteConfiguration:
RedirectAllRequestsTo:
HostName: !Ref DestinationDomain
Protocol: 'https'
Both Option 1 and Option 2 for subdomain redirection are relatively inexpensive, each serving very little data and using mostly free tier levels of resources (depending on your traffic volume). The main trade off is Option 1 gives you poor SEO performance, and Option 2 gives you a Public S3 bucket.
So is there an Option 3 that prioritizes SEO and Security?
Subdomain Redirection Option 3
Lets get fancy with Lambda@Edge
Lambda@Edge is a feature of Amazon CloudFront that lets you run code closer to users of your application, which can improve performance and reduce latency. With Lambda@Edge, you don't have to provision or manage infrastructure in multiple locations around the world. You pay only for the compute time you consume - there is no charge when your code is not running.
You can test this with 123cloudstreet.com or on your own with the Cloudformation template below.
Link to Cloudformation Template
Here's how we can use Lambda@Edge for a 301 redirect from a root domain to a subdomain.
Now when a request comes into your CloudFront distribution for the root domain, the Lambda function will be invoked. It will check the host header and return a 301 redirect to the www subdomain.
This approach has several benefits:
It's SEO-friendly. 301 redirects are the recommended way to permanently redirect URLs for SEO purposes.
It's secure. You don't need to make your S3 bucket public.
You don’t even need an S3 Bucket.This uses an empty private S3 bucket because CloudFront needs an origin configured.It's scalable. Lambda@Edge scales automatically with your CloudFront traffic.
It's customizable. You have full control over the redirect logic in the Lambda function which could allow you to setup multiple redirects or control different patterns.
The main drawback is that Lambda@Edge can be a bit more complex to set up compared to the other options. Also, keep in mind that Lambda@Edge is priced differently than regular Lambda functions.
Lambda@Edge charges for the total number of request for the function and for every GB-second.
Request pricing is $0.60 per 1 million requests ($0.0000006 per request).
Duration pricing is $0.00005001for every GB-second used
This means that “if you allocate 128MB of memory available per execution with your Lambda@Edge function, then your duration charge will be $0.00000625125 for every 128MB-second used, metered in 1ms granularity.”
Subdomain Redirection Option 4 - You probably want this one
Let's get even fancier with CloudFront Functions!
CloudFront Functions are a lightweight serverless computing service designed specifically for content delivery, enabling you to run code closer to your users for optimal performance and reduced latency. This service is integral to CloudFront and helps in executing simple functions rapidly at CloudFront edge locations worldwide. You pay only for the compute time you consume, so there is no charge when your code is not running.
You can test this with 123street.cloud or on your own with the provided CloudFormation template.
Link to CloudFormation Template
Here’s how we can utilize CloudFront Functions for a 301 redirect from a root domain to a subdomain or from a root domain to another root domain.
When a request reaches your CloudFront distribution for the root domain, the CloudFront Function will be executed. This function simply checks the request and returns a 301 redirect to the target subdomain.
This approach offers several benefits:
SEO-friendly: Implements 301 redirects, which are preferred for permanent URL redirection in SEO.
Secure: The simplicity of CloudFront Functions eliminates the need for additional security configurations typically required for server-based setups.
Scalable: CloudFront Functions are designed to handle high request volumes, scaling automatically with the increase in CloudFront traffic.
Customizable: Though
muchsimpler than Lambda@Edge, CloudFront Functions still offer the flexibility to define specific redirection behaviors based on the incoming request characteristics.
The main advantage over Lambda@Edge is the simplicity and lower cost, particularly for simple tasks like redirections. CloudFront Functions are priced at a MUCH lower rate than Lambda@Edge, costing $0.10 per 1 million invocations!
This means that using CloudFront Functions, you benefit from reduced complexity and cost while achieving functionality specific to content delivery scenarios such as URL redirection!
Root Domain to Subdomain Redirects Conclusion
In conclusion, options for redirecting traffic from a root domain to a subdomain on AWS vary in complexity, cost, SEO impact, and security considerations. The four methods outlined offer scalable solutions ranging from simple HTML meta-refresh redirects, to S3 and CloudFront-based 301 redirects, and finally to the more complex but highly flexible Lambda@Edge approach.
Option 1, using a meta-refresh HTML page, is the simplest to implement but is generally not recommended for SEO. Despite this, it serves as a quick and straightforward method for redirection, suitable for temporary or less critical redirection needs.
Option 2 leverages S3 for a 301 redirect, which is better for SEO as it conveys to search engines that the move is permanent. This method does require a public-facing S3 bucket, which may raise security concerns, though these can be mitigated with careful configuration.
Option 3, utilizing Lambda@Edge, represents a sophisticated approach that combines scalability, security, and SEO-friendliness. This method does not require a public bucket and allows for complex redirection logic to be implemented. While it is a bit more complex and very slightly more expensive regarding AWS charges, it arguably offers the best balance between functionality and best practices for professional use.
Option 4, utilizing CloudFront Functions provides SEO - no S3 bucket and the lowest cost with the same level of security. This is most likely your best option for redirects in all cases.
Want my generic prescriptive guidance? Use Option 4 if you care about SEO and Option 1 if you just want it done dirty. Get all the CloudFormation here.
Standalone Public S3 Buckets
Ah, the audacious charm of deploying standalone S3 public bucket websites—a culinary equivalent of serving a sizzling, rare steak in a vegan commune. It’s not just about throwing caution to the wind; it’s about embracing the grandiose belief that the open web is still a place of purity, much like believing in the sanctity of a street-side taco in Tijuana at 3 AM. This approach is the digital antithesis of a locked safe in a bank vault; it's more akin to leaving your front door wide open, inviting every passerby into the warm embrace of your data. Imagine striding confidently through the back alleys of Bangkok, your server logs fluttering in the breeze behind you, a testament to your unshakeable faith in the digital goodness of mankind. It's a reckless culinary adventure, liberally seasoned with potential calamities, yet thrilling in its defiance of the mundane best practices. So, pour yourself a glass of absinthe, toast to the boundless optimism of your pioneering spirit, and watch as the flavors of your bold decision mingle with the unpredictable spices of the world wide web, hoping the result is palatable, or at least, an unforgettable tale.
S3 + CloudFront Overview
A match made in heaven, S3 and CloudFront will allow you to host a static website in a cost conscious, secure way without the need to maintain any infrastructure.
There are 2 main patterns here for securing access between S3 and CloudFront: OAI(legacy) and OAC.
Origin Access Control (OAC) represents an significant advancement over Origin Access Identity (OAI) for securing Amazon S3 content delivered via CloudFront, primarily due to its integration with S3's Access Point policies and the simplification of access management. OAC enables precise control by leveraging AWS’s advanced policy-based management system, allowing for more granular and secure configurations that are aligned with least privilege access principles. While the transition to OAC offers improved security practices and streamlined configurations, it necessitates a comprehension of S3 Access Point policies and a complex migration for those already invested in OAI configurations. However, the benefits, including enhanced security measures, compliance capabilities, and future-proofing against evolving AWS features and best practices, strongly advocate for OAC’s adoption in modern cloud architectures.
Another topic we now need to discuss is CORS. So far, we have only been discussing redirection architecture, now that we’re hosting content, we need to make sure we use CORS. S3 and CloudFront both support Cross-Origin Resource Sharing (CORS), but they do so in slightly different manners, catering to their respective roles in content storage and distribution within the AWS ecosystem.
Amazon S3 and CORS:
Amazon S3 allows you to store and retrieve any amount of data at any time, from anywhere on the web. When it comes to CORS, S3 provides the capability to configure CORS settings for your buckets directly. This is crucial when your application hosted on one domain (e.g., www.example.com
) needs to directly access or manipulate resources stored in an S3 bucket that's not on the same origin.
Configuring CORS on an S3 bucket involves setting up a CORS configuration rule as JSON in the bucket's permissions settings. This configuration includes specifying which origins are allowed to access the bucket, what HTTP methods (GET, PUT, POST, DELETE, etc.) are permitted, whether credentials are supported, and what headers can be exposed to the browser.
For example, if you’re hosting a web application on www.example.com
that needs to retrieve assets from your S3 bucket (myapp-assets.s3.amazonaws.com
), you would configure the S3 bucket's CORS settings to allow GET
requests originating from www.example.com
. This would block someone else from trying to have badactorexample.com
from using your bucket to serve content on their site.
Amazon CloudFront and CORS:
CloudFront is a global content delivery network (CDN) service that accelerates the delivery of your websites, APIs, video content, or other web assets. It integrates with other Amazon Web Services products, like S3, to you an easy way to distribute content to end-users with low latency and high data transfer speeds.
When you use Amazon CloudFront to serve content stored in an S3 bucket, CORS configurations become slightly more complex since you're dealing with two services.
Here's how CORS works with CloudFront:
CORS Configuration on S3: First, you must set the CORS configuration on the S3 bucket as described above.
CloudFront Behavior: Next, when configuring your CloudFront distribution, you can set specific behaviors to forward
Origin
headers to your S3 bucket. CloudFront can either forward all headers to your origin (which can impact caching), forward a whitelist of headers (includingOrigin
for CORS), or not forward headers at all.Handling Preflight Requests: For preflight OPTIONS requests, you need to ensure your S3 bucket or application backend appropriately handles these requests and returns suitable CORS headers. CloudFront itself doesn't automatically respond to OPTIONS requests; the response depends on your origin’s CORS setup.
Caching based on CORS Headers: You can configure CloudFront to cache responses based on the
Origin
header, allowing you to efficiently serve requests for different origins with the correct CORS headers.
Ok great - so lets look at a best practice least privilege design for our first option.
S3 + CloudFront with OAI (Option 1)
OAI or Origin Access Identity is the legacy way of providing authentication between CloudFront and S3; however, it is still used and works just fine. This pattern is well documented across the internet but in the interest of providing a one-stop guide I’ll cover it here.
You can test this pattern at oai.onetwentythree.cloud
Link to CloudFormation template https://github.com/jeeshofone/Public-AWS-Website-Patterns/tree/main/S3%2BCloudfront%20with%20OAI
This AWS CloudFormation template creates a secure, private AWS S3 bucket paired with a CloudFront distribution, utilizing an Origin Access Identity (OAI) to strictly control access. It also sets up a Lambda function for lifecycle management tasks such as initially populating and optionally clearing the S3 bucket. The S3 bucket is configured with a stringent public access block policy and a CORS rule that only allows GET requests from the specified domain. An SSL certificate managed by AWS Certificate Manager is used to enforce HTTPS connections, while Route 53 is employed to set up DNS routing for the domain.
This is pretty straight forward; however, there are some things in the Cloudformation template I want to draw your attention to:
CORS is provided by the S3 bucket configuration
Nothing but the Lambda function has permissions to write the the S3 bucket.
A custom resource exists to trigger the lambda function on stack creation and removal to make and delete the index.html file.
You must have a domain in your aws account and be using route53 for this template to work.
This template uses ACM for certificates (free) but must be deployed in US-East-1 even though Cloudformation will provide your website at the edge globally.
Now lets look at what needs to change to migrate this from OAI to OAC
S3 + Cloudfront with OAC (Option 2) - You probably want this one
This is the difference between the OAI and the OAC template. We remove the Origin Access Identity and add the Origin Access Control in its place. Then inside the Cloudfront template we undefined the OAI and define the ID of the OAC. In the bucket policy we remove the access from the OAI and provide the access to the OAC.
“SO WHAT?” you might ask - “good question” I might reply.
Why pick OAC over OAI? The short answer is - it’s more secure, and is the supported method going forward. The long answer is here: https://aws.amazon.com/blogs/networking-and-content-delivery/amazon-cloudfront-introduces-origin-access-control-oac/
Link to OAC Cloudformation template - it is exactly the same as the OAI template, but with security between S3 and Cloudfront via OAC instead of OAI.
Conclusion
This blog post has outlined various strategies for deploying basic websites and domain redirects on AWS, utilizing services such as S3, CloudFront, Lambda@Edge, and more.
I hope this guide gets you started faster on your journey to deploying your next website.
What a great guide!!