LAB13_AWS_Route53 Hosted Zone, Alias Record, and Health Check

Route53 Hosted Zone, Alias Record, and Health Check

☁ AWS ⭐ Beginner Route53 Alias Records Health Checks Failover Routing

Scenario

Your application is now running behind an Application Load Balancer with a long, autogenerated DNS name. You need to create a user-friendly domain name like app.example.com that points to the ALB using a Route53 Alias record. You also want a health check so Route53 can detect if the endpoint goes down.

Your Objectives
  • Create or reference an aws_route53_zone for your domain.
  • Create an aws_route53_record of type A using an alias pointing to the ALB DNS name.
  • Create an aws_route53_health_check that monitors the /health endpoint.
  • Create a failover routing policy: primary record points to the ALB, secondary record points to an S3 static page as a maintenance fallback.
  • Output the hosted zone ID and the record FQDN.

Additional Context

Alias records are Route53-specific — they resolve to the IP addresses of AWS resources (ALBs, CloudFront, S3, etc.) without incurring extra DNS query fees and without needing a TTL. Never use a CNAME for the zone apex (example.com) — only alias records work for zone apex because of the DNS CNAME restriction.

For the ALB alias record, you need the zone_id of the ALB, not your Route53 hosted zone. The ALB has its own fixed hosted zone ID per region — use the aws_lb data source or resource attribute zone_id.

versions.tf

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}

variables.tf

variable "aws_region"        { type = string; default = "us-east-1" }
variable "domain_name"       { type = string; description = "Root domain, e.g. example.com" }
variable "subdomain"         { type = string; default = "app" }
variable "alb_dns_name"      { type = string; description = "DNS name of your Application Load Balancer." }
variable "alb_zone_id"       { type = string; description = "Hosted zone ID of the Application Load Balancer." }
variable "environment"       { type = string; default = "prod" }

main.tf

# main.tf

provider "aws" {
  region = var.aws_region
}

# Look up the existing hosted zone — we assume the domain is already registered.
# Use data source if the zone exists; use aws_route53_zone resource if creating new.
data "aws_route53_zone" "main" {
  name         = var.domain_name
  private_zone = false
}

# Health check — monitors the app endpoint every 30 seconds.
# Route53 uses this to determine if the primary record is healthy.
resource "aws_route53_health_check" "app" {
  fqdn              = "${var.subdomain}.${var.domain_name}"
  port              = 443
  type              = "HTTPS"
  resource_path     = "/health"
  failure_threshold = 3   # declare unhealthy after 3 consecutive failures
  request_interval  = 30  # check every 30 seconds

  tags = { Name = "app-health-check", Environment = var.environment }
}

# Primary A record — alias to the ALB.
# When health check fails, Route53 stops routing to this record.
resource "aws_route53_record" "app_primary" {
  zone_id = data.aws_route53_zone.main.zone_id
  name    = "${var.subdomain}.${var.domain_name}"
  type    = "A"

  # Failover routing: this record is the PRIMARY.
  failover_routing_policy {
    type = "PRIMARY"
  }

  set_identifier  = "primary"
  health_check_id = aws_route53_health_check.app.id

  # Alias to the ALB — no TTL needed for alias records.
  alias {
    name                   = var.alb_dns_name
    zone_id                = var.alb_zone_id
    evaluate_target_health = true
  }
}

# Secondary (failover) record — points to a static "maintenance" page on S3.
# Only active when the primary health check fails.
resource "aws_route53_record" "app_secondary" {
  zone_id = data.aws_route53_zone.main.zone_id
  name    = "${var.subdomain}.${var.domain_name}"
  type    = "CNAME"
  ttl     = 60

  failover_routing_policy {
    type = "SECONDARY"
  }

  set_identifier = "secondary"

  # Replace with your S3 static website endpoint if desired.
  records = ["maintenance.${var.domain_name}"]
}

outputs.tf

output "hosted_zone_id" { value = data.aws_route53_zone.main.zone_id }
output "app_fqdn"       { value = "${var.subdomain}.${var.domain_name}" }
output "health_check_id"{ value = aws_route53_health_check.app.id }

Workflow Commands

terraform init
terraform apply \
  -var="domain_name=example.com" \
  -var="alb_dns_name=web-alb-prod-xxx.us-east-1.elb.amazonaws.com" \
  -var="alb_zone_id=Z35SXDOTRQ7X7K"

# Test DNS resolution (may take a minute to propagate)
dig app.example.com

# Check health check status
aws route53 get-health-check-status \
  --health-check-id $(terraform output -raw health_check_id)

terraform destroy -var="domain_name=example.com" -var="alb_dns_name=..." -var="alb_zone_id=..."
✓ Expected Output After Apply
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

app_fqdn        = "app.example.com"
health_check_id = "12345678-abcd-1234-abcd-123456789012"
hosted_zone_id  = "Z0123456789ABCDEFGHIJ"