Secure Your Apps with ALB Path-Based Routing and HTTPS using Terraform

If you’ve followed along with Part 1 and Part 2 and Part 3 of my AWS-Terraform series, you’ve already built a solid VPC, deployed EC2 instances, and configured a basic ALB.
Now it’s time to bring your infrastructure closer to production-grade. In this tutorial, we’ll:
✅ Add TLS/SSL support with AWS ACM
✅ Create HTTPS listeners
✅ Route traffic based on URL paths like /app1
and /app2
✅ Attach multiple EC2 groups to the ALB
✅ Configure DNS with Route 53
Let’s break it down step by step.
✅ ACM Module – TLS Certificate for HTTPS
module "acm" {
source = "terraform-aws-modules/acm/aws"
version = "5.1.0"
- source: Uses the official ACM module from Terraform Registry to simplify certificate provisioning.
- version: Locks the module to a specific version for stability.
domain_name = trimsuffix(data.aws_route53_zone.mydomain.name, ".")
zone_id = data.aws_route53_zone.mydomain.zone_id
- domain_name: Main domain to issue the cert for.
trimsuffix()
removes the trailing dot. - zone_id: The ID of your Route 53 hosted zone where validation records will be created.
subject_alternative_names = ["*.rezaops.com"]
- Adds a wildcard certificate, allowing TLS on subdomains like
apps.rezaops.com
.
validation_method = "DNS"
wait_for_validation = true
- validation_method: Uses DNS validation (recommended for automation).
- wait_for_validation: Waits for Route 53 to validate the cert before continuing.
tags = local.common_tags
}
- Adds tags like
Environment
,Owner
, etc.
📤 ACM Output Block
output "acm_certificate_arn" {
description = "The ARN of the certificate"
value = module.acm.acm_certificate_arn
}
- Outputs the Amazon Resource Name of the issued cert.
- This value is passed into the ALB module for HTTPS listener setup.
🌐 ALB Module – Application Load Balancer Setup
module "alb" {
source = "terraform-aws-modules/alb/aws"
version = "9.11.0"
- Uses the ALB module to create listeners, target groups, and routing rules.
name = "${local.name}-alb"
load_balancer_type = "application"
vpc_id = module.vpc.vpc_id
subnets = module.vpc.public_subnets
security_groups = [module.loadbalancer_sg.security_group_id]
enable_deletion_protection = false
- name: Sets ALB name based on a local value.
- load_balancer_type: Application Load Balancer (Layer 7).
- vpc_id: Attaches ALB to the right VPC.
- subnets: Deploys ALB across public subnets.
- security_groups: Attaches an SG allowing ports 80 & 443.
- deletion_protection: Disabled here for dev/testing.
🔁 ALB Listener Block: HTTP → HTTPS Redirect
my-http-https-redirect = {
port = 80
protocol = "HTTP"
redirect = {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
- Redirects all insecure HTTP traffic to secure HTTPS
- status_code = HTTP_301: Permanent redirect (good for SEO & security)
🔒 ALB Listener Block: HTTPS + Path-Based Routing
my-https-listener = {
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-Res-2021-06"
certificate_arn = module.acm.acm_certificate_arn
- Listens on port 443
- SSL policy: Uses TLS 1.3 with modern cipher suite
- certificate_arn: Points to the ACM cert output earlier
🧪 HTTPS Fixed Response for Root Path
fixed_response = {
content_type = "text/plain"
message_body = "Fixed Static message - for Root Context"
status_code = "200"
}
- This sends a fixed response if someone visits the base domain
/
- Optional and often used for placeholder or landing messages
🎯 Path-Based Listener Rules
Rule 1: For /app1
myapp1-rule = {
actions = [{
type = "weighted-forward"
target_groups = [{
target_group_key = "mytg1"
weight = 1
}]
stickiness = {
enabled = true
duration = 3600
}
}]
conditions = [{
path_pattern = {
values = ["/app1*"]
}
}]
}
- path_pattern: Matches URLs like
/app1
,/app1/home
, etc. - weighted-forward: Sends all traffic to
mytg1
- stickiness: Ensures the same user gets the same backend for 1 hour
Rule 2: For /app2
(same logic)
myapp2-rule = {
actions = [{
type = "weighted-forward"
target_groups = [{
target_group_key = "mytg2"
weight = 1
}]
stickiness = {
enabled = true
duration = 3600
}
}]
conditions = [{
path_pattern = {
values = ["/app2*"]
}
}]
}
🧱 Target Groups for Each App
mytg1 – for /app1
mytg1 = {
create_attachment = false
name_prefix = "mytg1-"
port = 80
protocol = "HTTP"
target_type = "instance"
protocol_version = "HTTP1"
health_check = {
enabled = true
path = "/app1/index.html"
matcher = "200-399"
}
tags = local.common_tags
}
- create_attachment = false: We’ll attach EC2s manually
- health_check path must match a real file or endpoint in your app
mytg2 – for /app2
(same logic)
mytg2 = {
...
path = "/app2/index.html"
}
🔗 Target Group Attachments (Manual)
Attach EC2s for /app1
resource "aws_lb_target_group_attachment" "mytg1" {
for_each = { for k,v in module.ec2_private_app1: k => v }
target_group_arn = module.alb.target_groups["mytg1"].arn
target_id = each.value.id
port = 80
}
- Registers each EC2 in
module.ec2_private_app1
tomytg1
Attach EC2s for /app2
resource "aws_lb_target_group_attachment" "mytg2" {
for_each = { for k,v in module.ec2_private_app2: k => v }
target_group_arn = module.alb.target_groups["mytg2"].arn
target_id = each.value.id
port = 80
}
- Same logic, different app group
🌍 Route 53 DNS Record
resource "aws_route53_record" "apps_dns" {
zone_id = data.aws_route53_zone.mydomain.zone_id
name = "apps.rezaops.com"
type = "A"
alias {
name = module.alb.dns_name
zone_id = module.alb.zone_id
evaluate_target_health = true
}
}
📘 Explanation:
- Points
apps.rezaops.com
to the ALB using an alias record - evaluate_target_health: If targets are unhealthy, Route 53 won’t route traffic
✅ Wrapping Up
In this article, you learned how to:
- Issue a TLS certificate using AWS ACM and DNS validation
- Configure an ALB with HTTP to HTTPS redirection
- Set up path-based routing rules for different applications
- Create and attach multiple target groups
- Route traffic securely with a custom domain using Route 53
This setup is ideal for hosting multiple microservices or apps under a single domain with clean separation, secure routing, and strong observability options through ALB features.
🔜 Coming Next
In the next part of this series, I’ll walk you through setting up Host Header-Based Routing with your ALB.
You’ll learn how to:
- Route traffic to different target groups based on subdomains like
app1.rezaops.com
,app2.rezaops.com
- Add multiple SSL certificates (optional: SNI support)
- Update DNS records for clean and secure multi-host deployments
This approach is commonly used in multi-tenant architectures and is perfect when hosting multiple applications behind the same ALB.