If you need to set up a redirect for a domain to a subdomain and wonder how to do it in AWS, here is the answer. In my case, I wanted to redirect my apex domain (cntechy.com) to my blog subdomain (blog.cntechy.com). I also wanted https
redirect and www.
requests to redirect to the naked subdomain.
IaC tool: Terraform.
AWS services used: S3, Cloudfront, Cloudfront functions, Route 53, ACM.
We could try to do it with S3 alone, by naming the buckets the same as the domain name, but it has several limitations such as the lack of SSL/TLS support.
Cloudfront gives us the SSL/TLS capabilities and Cloudfront functions handle the redirection logic.
Create the S3 bucket
First, we will create a S3 bucket with the apex domain name (cntechy.com). Then, we will block all public access to the bucket. Then, we will enable static site hosting for S3.
# S3 bucket to host website content
resource "aws_s3_bucket" "web_app_bucket" {
bucket = var.domain_name
tags = {
Name = "s3 bucket for root to blog"
}
force_destroy = true
}
# Block all public access
resource "aws_s3_bucket_public_access_block" "s3_bucket_policy" {
bucket = aws_s3_bucket.web_app_bucket.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
# Static website configuration
resource "aws_s3_bucket_website_configuration" "web_app_bucket_config" {
bucket = aws_s3_bucket.web_app_bucket.id
index_document {
suffix = "index.html"
}
error_document {
key = "index.html"
}
}
Create the OAC and cache policy for Cloudfront distribution
Create an Origin Access Control (OAC) that secures the S3 bucket and makes it accessible only through the CloudFront distribution.
# Origin Access Control resource
resource "aws_cloudfront_origin_access_control" "root_redirects_app_policy" {
name = "root to blog redirects OAC"
description = "root to blog redirects OAC policy"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
Create a default cache policy.
# Default cache policy for Cloudfront
resource "aws_cloudfront_cache_policy" "root_redirects_default_cache_policy" {
name = "root_redirects-cache-policy"
comment = "root_redirects-cache-policy"
default_ttl = 50
max_ttl = 100
min_ttl = 1
parameters_in_cache_key_and_forwarded_to_origin {
cookies_config {
cookie_behavior = "all"
}
headers_config {
header_behavior = "none"
}
query_strings_config {
query_string_behavior = "all"
}
enable_accept_encoding_brotli = true
enable_accept_encoding_gzip = true
}
}
Create the CloudFront function
The block below will create a CloudFront function which will redirect domains based on the host header of the request.
resource "aws_cloudfront_function" "redirect_apex_to_blog" {
name = "redirect_apex_www_www_blog_to_blog_cntechy_com"
runtime = "cloudfront-js-1.0"
comment = "redirect apex, www* , www.blog.* to blog.cntechy.com"
publish = true
code = file("${path.module}/code/redirect.js")
}
The code for domain redirection runs in a modified JavaScript runtime which has a limited set of features. Ensure that the code is valid and can run in the runtime. You can read more about it here.
The code shown below will redirect all http/https requests to cntechy.com
, www.cntechy.com
and www.blog.cntechy.com
to blog.cntechy.com
while preserving the URI.
function handler(event) {
// Redirect from apex to subdomain (blog).
var request = event.request;
if (request.headers.host) {
var host = request.headers.host.value;
if (host === "cntechy.com" || host === "www.cntechy.com" || host === "www.blog.cntechy.com" ) {
return {
statusCode: 302,
statusDescription: "Found",
headers: {
location: { value: `https://blog.cntechy.com${request.uri}` }
},
};
}
}
return event.request;
}
Generate the certificates in Amazon Certificate Manager
To ensure that Cloudfront can serve https requests, we need to generate an SSL cert for all the domains we are going to serve content through.
In my case, I have 3 domains. My main domain is cntechy.com (which is var.domain_name
) is passed in as domain_name
argument. The remaining 2 domains will be listed under the subject_alternative_names
argument.
It is important to note that the certificate needs to be issued in us-east-1
region as it needs to be used by Cloudfront. Read more about the requirements here.
We will need to validate the certs against the domain to verify that we do indeed own the domain. This is done through the aws_acm_certificate_validation
resource block. This resource represents a successful validation of an ACM certificate.
# ACM cert for the domain name validation
resource "aws_acm_certificate" "domain_cert" {
provider = aws.us_east_1
domain_name = var.domain_name
validation_method = "DNS"
subject_alternative_names = [
"www.${var.domain_name}", "www.blog.${var.domain_name}"
]
lifecycle {
create_before_destroy = true
}
}
# validating the certs
resource "aws_acm_certificate_validation" "domain_cert_validation" {
provider = aws.us_east_1
certificate_arn = aws_acm_certificate.domain_cert.arn
validation_record_fqdns = [for record in aws_route53_record.validation : record.fqdn]
}
Add the DNS records in Route 53
This step is essential to complete the validation for issuance of the certs.
# record for domain name validation
resource "aws_route53_record" "validation" {
for_each = {
for dvo in aws_acm_certificate.domain_cert.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = data.aws_route53_zone.domain_hosted_id.zone_id
}
Create the CloudFront distribution
Finally, we can create the CloudFront distribution.
While creating the Cloudfront distribution, we need to add all the domains we want to serve under the aliases
argument.
We will associate the CloudFront function to the distribution with the function_association
block under the default_cache_behavior
block. The function will be executed for each incoming request as set up by the value of viewer-request
in the event_type
argument.
We will associate the certificate to the distribution with the viewer_certificate
block.
We will explicitly provide an array of dependencies just to ensure that this distribution is created only after all those dependencies have been created. In our case, these dependencies are the certificate, validation records in Route 53, and the CloudFront function.
# Cloudfront Distribution for S3
resource "aws_cloudfront_distribution" "root_redirects_app_cf" {
origin {
domain_name = aws_s3_bucket.web_app_bucket.bucket_regional_domain_name
origin_id = var.cf_s3_origin_id
origin_access_control_id = aws_cloudfront_origin_access_control.root_redirects_app_policy.id
}
aliases = [var.domain_name, "www.${var.domain_name}", "www.blog.${var.domain_name}"]
enabled = true
is_ipv6_enabled = true
comment = var.domain_name
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = var.cf_s3_origin_id
cache_policy_id = aws_cloudfront_cache_policy.root_redirects_default_cache_policy.id
viewer_protocol_policy = "redirect-to-https"
function_association {
event_type = "viewer-request"
function_arn = aws_cloudfront_function.redirect_apex_to_blog.arn
}
}
price_class = "PriceClass_All"
restrictions {
geo_restriction {
restriction_type = "none"
locations = []
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.domain_cert.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
depends_on = [ aws_acm_certificate.domain_cert, aws_route53_record.validation, aws_cloudfront_function.redirect_apex_to_blog ]
}
Create Route 53 records
We will create 3 DNS records to route the domains to Cloudfront. These records will be A records. AWS allows us to use Alias records under A type to point to any AWS resource. We will use the alias
block to point to the CloudFront distribution.
# Route 53 record for root domain
resource "aws_route53_record" "staticapp" {
zone_id = data.aws_route53_zone.domain_hosted_id.zone_id
name = var.domain_name
type = "A"
allow_overwrite = true
alias {
name = aws_cloudfront_distribution.root_redirects_app_cf.domain_name
zone_id = aws_cloudfront_distribution.root_redirects_app_cf.hosted_zone_id
evaluate_target_health = false
}
}
# Route 53 record for www domain
resource "aws_route53_record" "www_staticapp" {
zone_id = data.aws_route53_zone.domain_hosted_id.zone_id
name = "www.${var.domain_name}"
type = "A"
allow_overwrite = true
alias {
name = aws_cloudfront_distribution.root_redirects_app_cf.domain_name
zone_id = aws_cloudfront_distribution.root_redirects_app_cf.hosted_zone_id
evaluate_target_health = false
}
}
# Route 53 record for www blog domain
resource "aws_route53_record" "www_blog_staticapp" {
zone_id = data.aws_route53_zone.domain_hosted_id.zone_id
name = "www.blog.${var.domain_name}"
type = "A"
allow_overwrite = true
alias {
name = aws_cloudfront_distribution.root_redirects_app_cf.domain_name
zone_id = aws_cloudfront_distribution.root_redirects_app_cf.hosted_zone_id
evaluate_target_health = false
}
}
Deploy the project in AWS using Terraform
Initialize the terraform.
terraform init
Validate terraform.
terraform validate
Execute terraform plan
command. This command creates an execution plan, which lets you preview the changes that Terraform plans to make.
terraform plan
Execute terraform. When running the command below, you should provide all the required variable values in the terraform.tfvars
file.
terraform apply
Verify
After the successful creation of all the resources, in my case, if you navigate to any of the domains cntechy.com
, www.cntechy.com
, www.blog.cntechy.com
, you will be redirected to the blog.cntechy.com.
You can open the Dev Tools and observe the requests being made. If you request the apex naked domain cntechy.com
the request is made as an http request. This will be redirected to https domain with 301 status code. This is done by Cloudfront as we have put the viewer_protocol_policy
as redirect-to-https
.
Then, the https request will be redirected to blog.cntechy.com by the CloudFront distribution. The redirect status code is 302 as set by us in the function. You can verify that it was indeed done by the CloudFront function as we can see the X-Cache
header of FunctionGeneratedResponse from cloudfront
Then the request is made to blog.cntechy.com which is successful with a status code of 200.
Github repo
The final code of this article can be found at https://github.com/this-santhoshss/site-redirection-s3-cloudfront