If you want to serve Web content from a domain apex (example.com rather than http://www.example.com) then you need to add DNS A records containing the IP addresses of target servers to the domain. If you’re using Route53 (AWS’s service for domain name registration and DNS), then Route53 handles this automatically for you.

In a recent project, DNS and domain name registration was handled outside of AWS, so the Route53 option wasn’t available. This meant I needed to put together a way to have static IP addresses (IP addresses that don’t change) within AWS to serve Web content.

The first option I thought of was to start up an EC2 instance in a public subnet for this task.

There’s a few issues with this approach:

  • The EC2 instance itself must run a Web server, so it needs to be appropriately hardened, intrusion detection software added etc. which is extra work.
  • If the Web server needs to handle TLS traffic, then the TLS certificate must be present on the EC2 instance.
  • There’s no load balancing, so if the server goes down or can’t handle the load, the site would be down.

The next option was to use the relatively new Network Load Balancer feature to handle incoming traffic. This has a static IP address, but can pipe traffic to backend IP addresses.

Behind the Network Load Balancer, I placed an Application Load Balancer to use as a Web server to redirect traffic away from the domain apex to a subdomain (http://www.example.com) where I could serve traffic using CloudFront.

Using an Application Load Balancer instead of an EC2 instance has several benefits:

  • It doesn’t need to be patched, secured etc.
    It scales automatically.
    It integrates with Amazon Certificate Manager (in its local region, no us-east-1 like CloudFront) to provide TLS support.
    It has built in redirect features, and can even execute Lambdas now.
  • I decided to use the built-in redirect feature to redirect HTTP to HTTPS, and redirect from example.com to http://www.example.com so that I could serve the static content (generated using Prismic and Gatsby) using CloudFront.

Serving content with CloudFront off a domain apex is straightforward: Create a CloudFront distribution using your S3 bucket as a content origin server, then apply a CNAME of http://www.example.com to your domain with its value set to the CloudFront distribution’s domain name (typically example.cloudfront.net).

Here’s the CloudFormation template to get that up-and-running:

Once you’ve generated TLS certificates within us-east-1, you can apply them and you’ve got a very scalable site.

This all sounds simple, but there’s one real problem left on this part of the solution. The Network Load Balancer needs to forward on to the Application Load Balancer, but needs to use an IP address as a target. The IP addresses of the Application Load Balancers will change over time, so how can we handle that?

AWS provide CloudFormation template for just this situation found in this blog post. https://aws.amazon.com/blogs/networking-and-content-delivery/using-static-ip-addresses-for-application-load-balancers/ It’s frankly, a bit complicated, given that it runs a Lambda every few minutes to carry out a DNS lookup and adjust the Network Load Balancer targets, but the provided template works well and I haven’t had any production issues.

So, we’ve now got a redirect in place and need to get the CloudFront content running. Typically, there’s a few things to deal with when migrating a site:

1. Making sure that links from our old site get redirected to an appropriate place in our new site.

2. Handling subdirectories in S3 (e.g. https://example.com/careers) since S3 only serves up index.html automatically in the root of an S3 bucket.

While this could all be done by executing AWS Lambda functions directly from the Application Load Balancer, that would be a bit slower, since the Lambda would have to collect the content from S3 and then serve it, so I opted to use Lambda@Edge to execute custom code within the CloudFront distribution.

I couldn’t find an example that did both redirects and default documents, but it was easy enough to write. The main issue was poor documentation and a slow workflow for deployment.

Surprisingly complex for a simple thing…