Most teams use Route 53 as a place to set DNS records and forget about it. That works for simple setups, but Route 53 has a routing layer that most teams leave entirely unused. It can route users to the closest healthy endpoint based on latency, fail over to a secondary region automatically when primary health checks fail, split traffic across endpoints for canary deployments, and restrict internal service discovery to private hosted zones that are invisible from the public internet.
In this article I will walk through the routing policies that matter in production, health check configuration, private hosted zones for internal service discovery, and the multi-region failover architecture that keeps applications reachable during regional incidents.
Routing Policies Overview
Route 53 supports several routing policies. Simple routing returns a single value. Weighted routing splits traffic by percentage. Latency routing sends users to the region with the lowest latency from their location. Failover routing sends traffic to a primary endpoint and switches to a secondary when the primary fails health checks. Geolocation routing routes based on the user’s geographic location. Geoproximity routing factors in bias values to expand or shrink regions. IP-based routing routes based on the user’s IP CIDR range.
Health Checks
resource "aws_route53_health_check" "primary" {
fqdn = "app.us-east-1.yourdomain.com"
port = 443
type = "HTTPS"
resource_path = "/health"
failure_threshold = 3
request_interval = 10
regions = ["us-east-1", "eu-west-1", "ap-southeast-1"]
enable_sni = true
search_string = "\"status\":\"ok\""
inverted = false
tags = {
Name = "primary-region-health"
Environment = "production"
}
}
resource "aws_route53_health_check" "secondary" {
fqdn = "app.eu-west-1.yourdomain.com"
port = 443
type = "HTTPS"
resource_path = "/health"
failure_threshold = 3
request_interval = 10
regions = ["us-east-1", "eu-west-1", "ap-southeast-1"]
enable_sni = true
search_string = "\"status\":\"ok\""
tags = { Name = "secondary-region-health" }
}
resource "aws_cloudwatch_metric_alarm" "primary_health" {
alarm_name = "route53-primary-health"
comparison_operator = "LessThanThreshold"
evaluation_periods = 1
metric_name = "HealthCheckStatus"
namespace = "AWS/Route53"
period = 60
statistic = "Minimum"
threshold = 1
alarm_description = "Primary region health check failing"
alarm_actions = [aws_sns_topic.alerts.arn]
dimensions = {
HealthCheckId = aws_route53_health_check.primary.id
}
}
Checking from three regions rather than one eliminates false positives from Route 53 checker failures in a single location. Route 53 marks the health check as failed only if the majority of regions report failure. With three regions, two must agree before failover triggers. request_interval = 10 means checks happen every 10 seconds, which detects failures within 30 seconds with a threshold of 3. This is faster than most CloudWatch alarms, which have a minimum period of 60 seconds.
Failover Routing
resource "aws_route53_zone" "main" {
name = "yourdomain.com"
}
resource "aws_route53_record" "primary" {
zone_id = aws_route53_zone.main.zone_id
name = "app.yourdomain.com"
type = "A"
set_identifier = "primary"
failover_routing_policy {
type = "PRIMARY"
}
health_check_id = aws_route53_health_check.primary.id
alias {
name = aws_lb.primary.dns_name
zone_id = aws_lb.primary.zone_id
evaluate_target_health = true
}
}
resource "aws_route53_record" "secondary" {
zone_id = aws_route53_zone.main.zone_id
name = "app.yourdomain.com"
type = "A"
set_identifier = "secondary"
failover_routing_policy {
type = "SECONDARY"
}
alias {
name = aws_lb.secondary.dns_name
zone_id = aws_lb.secondary.zone_id
evaluate_target_health = true
}
}
The secondary record has no health check because it is the fallback. Route 53 only routes to the secondary when the primary health check fails. evaluate_target_health = true on both alias records means Route 53 also checks the ALB health before routing to it, catching cases where the load balancer itself is healthy but has no healthy targets behind it.
Latency Routing for Multi-Region
resource "aws_route53_record" "us_east" {
zone_id = aws_route53_zone.main.zone_id
name = "api.yourdomain.com"
type = "A"
set_identifier = "us-east-1"
latency_routing_policy {
region = "us-east-1"
}
health_check_id = aws_route53_health_check.us_east.id
alias {
name = aws_lb.us_east.dns_name
zone_id = aws_lb.us_east.zone_id
evaluate_target_health = true
}
}
resource "aws_route53_record" "eu_west" {
zone_id = aws_route53_zone.main.zone_id
name = "api.yourdomain.com"
type = "A"
set_identifier = "eu-west-1"
latency_routing_policy {
region = "eu-west-1"
}
health_check_id = aws_route53_health_check.eu_west.id
alias {
name = aws_lb.eu_west.dns_name
zone_id = aws_lb.eu_west.zone_id
evaluate_target_health = true
}
}
With latency routing and health checks, Route 53 sends each user to the region with the lowest latency from their location, but automatically routes around an unhealthy region. A user in Europe goes to eu-west-1 normally. If eu-west-1 fails its health check, Route 53 routes them to us-east-1 instead. This combines performance optimization and automatic failover in one routing policy.
Private Hosted Zones for Internal Service Discovery
resource "aws_route53_zone" "internal" {
name = "internal.yourdomain.com"
vpc {
vpc_id = var.vpc_id
}
tags = { Environment = "production" }
}
resource "aws_route53_record" "database" {
zone_id = aws_route53_zone.internal.zone_id
name = "db.internal.yourdomain.com"
type = "CNAME"
ttl = 60
records = [aws_db_instance.main.address]
}
resource "aws_route53_record" "cache" {
zone_id = aws_route53_zone.internal.zone_id
name = "cache.internal.yourdomain.com"
type = "CNAME"
ttl = 60
records = [aws_elasticache_replication_group.main.primary_endpoint_address]
}
Private hosted zones resolve only within associated VPCs. internal.yourdomain.com is invisible from the public internet. Your applications reference db.internal.yourdomain.com rather than the raw RDS endpoint. When you promote a read replica, change a database instance, or add an ElastiCache cluster, you update one DNS record and all services that reference it pick up the change on the next TTL expiry. No application deployments required.
Closing Thoughts
Route 53 is one of those services where the default usage covers maybe 20 percent of what is available. Health checks with multi-region checking, latency routing for global applications, automatic failover for disaster recovery, and private hosted zones for internal service discovery are all production-grade capabilities that require minimal ongoing maintenance once configured.
Set up health checks and alarms before you need failover. Use private hosted zones from the beginning so your application configuration never contains raw AWS endpoint strings. Configure latency routing if you run in multiple regions. These are the Route 53 decisions that matter when something goes wrong at 2am.
Enjoy the cloud.
Osama
#AWS #Route53 #DNS #CloudArchitecture #DisasterRecovery #Terraform #InfrastructureAsCode #AmazonWebServices #SolutionsArchitect #CloudComputing #NetworkEngineering #HealthChecks #MultiRegion #TechBlog #CloudInfrastructure #TrafficManagement #HighAvailability #SRE #Failover #DevOps
Leave a Reply