DevOps from Zero to Hero: DNS, TLS, and Making Your App Reachable

2026-05-18 | Gabriel Garrido | 21 min read
Share:

Support this blog

If you find this content useful, consider supporting the blog.

Introduction

Welcome to article ten of the DevOps from Zero to Hero series. In article eight we deployed our TypeScript API to ECS with Fargate and put an Application Load Balancer in front of it. That setup works, but right now the only way to reach the API is through an ugly auto-generated ALB hostname like task-api-alb-123456789.us-east-1.elb.amazonaws.com. No one wants to type that into a browser, and no one should be sending real traffic over plain HTTP.


In this article we are going to fix both of those problems. We will register a domain name, set up DNS so that a clean URL like api.yourdomain.com points to our load balancer, and configure TLS so all traffic is encrypted with HTTPS. By the end, your application will be reachable at a real domain with a valid certificate, exactly like a production service should be.


Let’s get into it.


DNS fundamentals: how your browser finds a server

DNS (Domain Name System) is the phonebook of the internet. When you type google.com into your browser, your computer does not magically know which IP address to connect to. It asks the DNS system, and DNS translates that human-readable name into a machine-readable IP address.


Here is the simplified flow of what happens when you visit api.yourdomain.com:


1. Browser asks: "What is the IP for api.yourdomain.com?"
2. Your OS checks its local cache. Not found.
3. Your OS asks your ISP's recursive resolver.
4. Resolver asks a root nameserver: "Who handles .com?"
5. Root says: "Ask the .com TLD nameserver."
6. Resolver asks the .com TLD: "Who handles yourdomain.com?"
7. TLD says: "Ask ns-1234.awsdns-56.org (the authoritative nameserver)."
8. Resolver asks the authoritative nameserver: "What is api.yourdomain.com?"
9. Authoritative nameserver responds: "It is an A record pointing to 54.23.45.67."
10. Browser connects to 54.23.45.67.

This entire process usually takes less than 100 milliseconds. Once resolved, the result is cached at multiple levels so subsequent requests are almost instant.


DNS record types you need to know

DNS is not just about mapping names to IP addresses. There are several record types, each serving a different purpose:


  • A record: Maps a domain name to an IPv4 address. Example: api.yourdomain.com -> 54.23.45.67. This is the most basic record type.
  • AAAA record: Same as A, but for IPv6 addresses. Example: api.yourdomain.com -> 2600:1f18:243:.... As IPv6 adoption grows, you will see more of these.
  • CNAME record: Maps a domain name to another domain name (an alias). Example: www.yourdomain.com -> yourdomain.com. The resolver follows the chain until it reaches an A record. Important: you cannot use a CNAME at the zone apex (the bare domain like yourdomain.com).
  • NS record: Specifies the authoritative nameservers for a domain. When you register a domain, the registrar needs to know which nameservers hold the DNS records for that domain.
  • MX record: Specifies mail servers for a domain. Not relevant for this article, but you will encounter these if you ever set up email.
  • TXT record: Holds arbitrary text. Used for domain verification (proving you own a domain), SPF records for email, and TLS certificate validation (which we will use later).

TTL: how long DNS answers are cached

Every DNS record has a TTL (Time To Live), measured in seconds. It tells resolvers how long to cache the answer before asking again.


  • High TTL (86400 = 24 hours): Good for records that rarely change. Reduces DNS queries, faster for users. Bad for quick changes since you have to wait for caches to expire.
  • Low TTL (60 = 1 minute): Good when you expect to change records frequently (during migrations, failovers). More DNS queries, slightly higher latency on first request.
  • Common strategy: Keep TTL high for normal operations. Before a planned migration, lower the TTL a day or two in advance, do the change, verify it works, then raise the TTL back up.

A common mistake is forgetting about TTL when doing a migration. If your TTL is 24 hours and you change an A record, some users will still be hitting the old IP for up to 24 hours. Plan ahead.


Route53: AWS’s DNS service

Route53 is AWS’s managed DNS service. It is highly available, globally distributed, and integrates natively with other AWS services. The name comes from the fact that DNS uses port 53.


The core concept in Route53 is the hosted zone. A hosted zone is a container for DNS records that belong to a single domain. When you create a hosted zone for yourdomain.com, Route53 assigns four nameservers to it. You then configure your domain registrar to point at those nameservers.


There are two types of hosted zones:


  • Public hosted zone: Resolves queries from the public internet. This is what you need for your user-facing application.
  • Private hosted zone: Resolves queries only within a VPC. Useful for internal service discovery (e.g., database.internal.yourdomain.com).

Route53 routing policies

Route53 supports several routing policies that go beyond simple “name to IP” resolution:


  • Simple routing: One record, one or more values. Route53 returns all values in random order. This is the default and what we will use in this article.
  • Weighted routing: Distribute traffic across multiple resources by weight. For example, send 90% of traffic to version 1 and 10% to version 2. Great for canary deployments.
  • Failover routing: Active-passive setup. Route53 sends traffic to the primary resource. If a health check fails, it automatically switches to a secondary resource.
  • Latency-based routing: Route users to the region with the lowest latency. If you have servers in us-east-1 and eu-west-1, European users automatically get routed to eu-west-1.
  • Geolocation routing: Route based on the user’s geographic location. Useful for compliance (keep EU user data in the EU) or serving localized content.

For most applications starting out, simple routing is all you need. As you grow and deploy to multiple regions, the other policies become incredibly valuable.


Domain registration and nameserver delegation

Before you can use Route53 for DNS, you need a domain name. You have two options:


  • Register through Route53: AWS acts as both your registrar and DNS provider. This is the simplest option because the nameserver delegation happens automatically.
  • Register elsewhere and delegate to Route53: Buy your domain from a registrar like Namecheap, GoDaddy, or Cloudflare, then update the nameservers to point at the Route53 hosted zone’s NS records.

If you registered your domain elsewhere, the process looks like this:


1. Create a hosted zone in Route53 for yourdomain.com
2. Route53 assigns four NS records (e.g., ns-1234.awsdns-56.org)
3. Go to your registrar's dashboard
4. Replace the default nameservers with the four Route53 NS records
5. Wait for propagation (can take up to 48 hours, usually much faster)
6. Now Route53 is authoritative for yourdomain.com

Once the delegation is complete, any DNS records you create in your Route53 hosted zone will be the ones the internet sees.


Route53 Alias records: a special AWS feature

Standard DNS has a limitation: you cannot put a CNAME record at the zone apex (the bare domain like yourdomain.com). This is a problem because AWS resources like ALBs and CloudFront distributions do not have static IP addresses, so you cannot use an A record either.


Route53 solves this with Alias records. An Alias record looks like an A or AAAA record to DNS clients, but behind the scenes it resolves to another AWS resource. Think of it as a CNAME that works at the zone apex.


  • No charge: Route53 does not charge for queries to Alias records that point at AWS resources.
  • Zone apex compatible: You can create an Alias record for yourdomain.com pointing at your ALB.
  • Health check aware: Alias records can inherit health checks from the target resource.

We will use an Alias record to point our domain at our Application Load Balancer.


TLS/SSL: why HTTPS matters

Right now our ALB is serving traffic over HTTP on port 80. Every request and response travels across the internet in plain text. Anyone between the user and your server (ISPs, Wi-Fi operators, anyone on the same network) can read the data, modify it, or inject content.


TLS (Transport Layer Security) encrypts the connection between the user’s browser and your server. When you see the padlock icon and https:// in your browser, that means TLS is in use.


Why HTTPS matters:


  • Confidentiality: Data in transit is encrypted. Passwords, API keys, personal information are all protected.
  • Integrity: Data cannot be modified in transit. No one can inject ads, malware, or tracking scripts into your responses.
  • Authentication: The certificate proves that the server is who it claims to be. This prevents man-in-the-middle attacks.
  • SEO and trust: Google has used HTTPS as a ranking signal since 2014. Browsers mark HTTP sites as “Not Secure.” Users trust HTTPS sites more.
  • Required for modern features: HTTP/2, service workers, geolocation API, and many other browser features require HTTPS.

In short, there is no good reason to serve production traffic over HTTP.


How TLS works (the short version)

When your browser connects to an HTTPS server, a process called the TLS handshake happens before any application data is exchanged:


Client                              Server
  |                                    |
  |--- ClientHello (supported ciphers) -->|
  |                                    |
  |<-- ServerHello + Certificate -------|
  |                                    |
  |--- Key exchange material ---------->|
  |                                    |
  |<-- Key exchange material -----------|
  |                                    |
  |   (Both sides derive session key)  |
  |                                    |
  |<== Encrypted application data ====>|

  • ClientHello: The browser sends the TLS versions and cipher suites it supports.
  • ServerHello: The server picks a cipher suite and sends its certificate (which contains the server’s public key).
  • Certificate validation: The browser checks that the certificate was issued by a trusted Certificate Authority (CA), is not expired, and matches the domain name.
  • Key exchange: Both sides exchange key material and derive a shared session key.
  • Encrypted communication: All subsequent data is encrypted with the session key using symmetric encryption (much faster than asymmetric).

The important takeaway is that you need a valid TLS certificate for your domain. That is where AWS Certificate Manager comes in.


AWS Certificate Manager (ACM)

ACM is a free service that lets you provision, manage, and deploy TLS certificates for use with AWS services like ALB, CloudFront, and API Gateway.


The key benefits:


  • Free: Public certificates are free when used with AWS services. No need to pay a CA.
  • Auto-renewal: ACM automatically renews certificates before they expire. No more 3 AM pages because a certificate expired.
  • DNS validation: You prove domain ownership by adding a CNAME record to your DNS. This is fully automatable with Terraform.
  • Managed private keys: ACM stores the private key securely. You never have to handle it yourself.

The process for getting a certificate with ACM looks like this:


1. Request a certificate for api.yourdomain.com (and optionally *.yourdomain.com)
2. ACM gives you a CNAME record to add to your DNS
3. You add the CNAME record to your Route53 hosted zone
4. ACM validates that you own the domain
5. Certificate is issued (usually within minutes)
6. ACM auto-renews it every 13 months

DNS validation is preferred over email validation because it can be fully automated and does not require someone to click a link in an email.


Connecting it all: the full picture

Let’s put together everything we have discussed. Here is how all the pieces connect to make your application reachable over HTTPS at a real domain:


User's browser
     |
     | (DNS query: api.yourdomain.com)
     v
Route53 hosted zone
     |
     | (Alias record -> ALB)
     v
Application Load Balancer
     |
     |--- Port 443 (HTTPS) -> Forward to target group (with ACM certificate)
     |--- Port 80  (HTTP)  -> Redirect to HTTPS
     |
     v
ECS Fargate tasks (your API containers)

  • Route53 resolves api.yourdomain.com to the ALB’s address using an Alias record.
  • ACM provides the TLS certificate that the ALB uses for HTTPS termination.
  • ALB terminates TLS, meaning the encrypted connection ends at the ALB. Traffic between the ALB and your ECS tasks travels over HTTP within your VPC (which is fine because it is on a private network).
  • HTTP to HTTPS redirect: The ALB listens on port 80 and automatically redirects to port 443, so users who type http:// still end up on HTTPS.

Terraform: Route53 hosted zone

Let’s write the Terraform code. We will build on the infrastructure from article eight. First, the Route53 hosted zone:


# dns.tf
variable "domain_name" {
  description = "The root domain name"
  type        = string
  default     = "yourdomain.com"
}

variable "api_subdomain" {
  description = "Subdomain for the API"
  type        = string
  default     = "api"
}

resource "aws_route53_zone" "main" {
  name = var.domain_name

  tags = {
    Name = var.domain_name
  }
}

This creates a public hosted zone. Route53 automatically creates the NS and SOA records. After applying this, you need to copy the four NS records and configure them at your domain registrar. You only need to do this once.


You can output the nameservers so you know what to set:


output "nameservers" {
  description = "Nameservers for the hosted zone. Set these at your registrar."
  value       = aws_route53_zone.main.name_servers
}

Terraform: ACM certificate with DNS validation

Next, we request a TLS certificate and validate it automatically through DNS:


# acm.tf
resource "aws_acm_certificate" "app" {
  domain_name               = "${var.api_subdomain}.${var.domain_name}"
  subject_alternative_names = ["*.${var.domain_name}"]
  validation_method         = "DNS"

  lifecycle {
    create_before_destroy = true
  }

  tags = {
    Name = "${var.api_subdomain}.${var.domain_name}"
  }
}

resource "aws_route53_record" "acm_validation" {
  for_each = {
    for dvo in aws_acm_certificate.app.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         = aws_route53_zone.main.zone_id
}

resource "aws_acm_certificate_validation" "app" {
  certificate_arn         = aws_acm_certificate.app.arn
  validation_record_fqdns = [for record in aws_route53_record.acm_validation : record.fqdn]
}

Let’s break this down:


  • aws_acm_certificate requests the certificate. We request it for api.yourdomain.com with a wildcard SAN (*.yourdomain.com) so it covers any subdomain.
  • validation_method = "DNS" tells ACM we will prove domain ownership by adding DNS records.
  • create_before_destroy = true ensures that when renewing, the new certificate is created before the old one is destroyed. This prevents downtime.
  • aws_route53_record.acm_validation creates the CNAME records that ACM requires for validation. The for_each loop handles the case where the certificate covers multiple domain names.
  • aws_acm_certificate_validation is a waiter resource. Terraform will block here until ACM confirms the certificate is validated and issued. This usually takes 2-5 minutes.

Terraform: ALB HTTPS listener and HTTP redirect

In article eight, we created an ALB with only an HTTP listener. Now we are going to add an HTTPS listener and change the HTTP listener to redirect to HTTPS:


# alb.tf (updated)

# Change the existing HTTP listener to redirect
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.app.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type = "redirect"

    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

# Add HTTPS listener
resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.app.arn
  port              = 443
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
  certificate_arn   = aws_acm_certificate_validation.app.certificate_arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

Important details:


  • The HTTP listener now returns a 301 redirect to HTTPS. This is a permanent redirect, so browsers and search engines will remember it and go directly to HTTPS next time.
  • The HTTPS listener references the validated ACM certificate. Note that we reference aws_acm_certificate_validation.app.certificate_arn, not the certificate directly. This ensures Terraform waits for validation to complete before creating the listener.
  • ssl_policy controls which TLS versions and cipher suites the ALB accepts. ELBSecurityPolicy-TLS13-1-2-2021-06 supports TLS 1.2 and 1.3, which is the current best practice. Older policies that allow TLS 1.0 or 1.1 should not be used.

You also need to update the ALB security group to allow HTTPS traffic:


# security_groups.tf (updated)
resource "aws_security_group" "alb" {
  name        = "${var.project_name}-alb-sg"
  description = "Security group for the Application Load Balancer"
  vpc_id      = aws_vpc.main.id

  ingress {
    description = "HTTP from anywhere (for redirect)"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTPS from anywhere"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.project_name}-alb-sg"
  }
}

We keep port 80 open so that the redirect works. If you close port 80, users who type http:// will get a connection timeout instead of a redirect.


Terraform: Route53 record pointing to the ALB

Finally, we create the DNS record that points our domain at the load balancer:


# dns.tf (continued)
resource "aws_route53_record" "api" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "${var.api_subdomain}.${var.domain_name}"
  type    = "A"

  alias {
    name                   = aws_lb.app.dns_name
    zone_id                = aws_lb.app.zone_id
    evaluate_target_health = true
  }
}

This is an Alias record. Even though it is type = "A", it does not contain a hardcoded IP address. Instead, it points to the ALB’s DNS name. Route53 resolves the ALB’s current IP addresses behind the scenes and returns them to the client.


evaluate_target_health = true means that if the ALB has no healthy targets, Route53 will not return this record in DNS queries. This is useful in multi-region setups with failover routing.


Health checks

Health checks are how AWS determines whether your application is actually working. There are two levels of health checks in our setup:


ALB target group health checks


We already configured these in article eight. The ALB periodically sends HTTP requests to your ECS tasks on a path you define (like /health). If a task fails consecutive checks, the ALB stops sending it traffic and ECS replaces it.


# Already in our target group from article 8
health_check {
  enabled             = true
  healthy_threshold   = 3
  unhealthy_threshold = 3
  timeout             = 5
  interval            = 30
  path                = "/health"
  protocol            = "HTTP"
  matcher             = "200"
}

Route53 health checks


Route53 health checks operate at the DNS level. They monitor an endpoint and can trigger DNS failover if the endpoint goes down. These are particularly useful when you have resources in multiple regions:


# route53_health.tf
resource "aws_route53_health_check" "api" {
  fqdn              = "${var.api_subdomain}.${var.domain_name}"
  port               = 443
  type               = "HTTPS"
  resource_path      = "/health"
  failure_threshold  = 3
  request_interval   = 30
  measure_latency    = true

  tags = {
    Name = "${var.api_subdomain}.${var.domain_name}-health-check"
  }
}

  • type = "HTTPS" means Route53 connects over TLS to check the endpoint.
  • failure_threshold = 3 means three consecutive failures mark the endpoint as unhealthy.
  • request_interval = 30 checks every 30 seconds. You can set this to 10 for faster detection, but it costs more.
  • measure_latency = true tracks latency metrics in CloudWatch.

For a single-region setup, Route53 health checks are optional but nice to have for monitoring. For multi-region with failover routing, they are essential.


The full Terraform configuration

Let’s put it all together in one view so you can see how the pieces connect:


# Full dns.tf
variable "domain_name" {
  description = "The root domain name"
  type        = string
  default     = "yourdomain.com"
}

variable "api_subdomain" {
  description = "Subdomain for the API"
  type        = string
  default     = "api"
}

# Hosted zone
resource "aws_route53_zone" "main" {
  name = var.domain_name

  tags = {
    Name = var.domain_name
  }
}

# ACM certificate
resource "aws_acm_certificate" "app" {
  domain_name               = "${var.api_subdomain}.${var.domain_name}"
  subject_alternative_names = ["*.${var.domain_name}"]
  validation_method         = "DNS"

  lifecycle {
    create_before_destroy = true
  }

  tags = {
    Name = "${var.api_subdomain}.${var.domain_name}"
  }
}

# DNS validation records for ACM
resource "aws_route53_record" "acm_validation" {
  for_each = {
    for dvo in aws_acm_certificate.app.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         = aws_route53_zone.main.zone_id
}

# Wait for certificate validation
resource "aws_acm_certificate_validation" "app" {
  certificate_arn         = aws_acm_certificate.app.arn
  validation_record_fqdns = [for record in aws_route53_record.acm_validation : record.fqdn]
}

# DNS record pointing to ALB
resource "aws_route53_record" "api" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "${var.api_subdomain}.${var.domain_name}"
  type    = "A"

  alias {
    name                   = aws_lb.app.dns_name
    zone_id                = aws_lb.app.zone_id
    evaluate_target_health = true
  }
}

# Outputs
output "nameservers" {
  description = "Nameservers for the hosted zone. Set these at your registrar."
  value       = aws_route53_zone.main.name_servers
}

output "app_url" {
  description = "The HTTPS URL for the API"
  value       = "https://${var.api_subdomain}.${var.domain_name}"
}

Applying the changes

Run terraform plan first to see what will be created:


terraform plan

You should see new resources for the hosted zone, ACM certificate, validation records, HTTPS listener, and the DNS record. Once you are satisfied with the plan:


terraform apply

The aws_acm_certificate_validation resource will block until the certificate is validated. This usually takes 2-5 minutes. If it takes longer than 10 minutes, check that the validation CNAME records were created correctly in the hosted zone and that your nameservers are properly delegated.


After the apply completes, update your nameservers at your registrar if you have not already. Then verify everything works:


# Check DNS resolution
dig api.yourdomain.com

# Test HTTPS
curl -v https://api.yourdomain.com/health

# Test HTTP redirect
curl -v http://api.yourdomain.com/health
# Should return a 301 redirect to https://

Debugging DNS issues

DNS problems are some of the most frustrating to debug because of caching. Here are the tools and techniques you need:


# Query a specific nameserver directly (bypass cache)
dig @ns-1234.awsdns-56.org api.yourdomain.com

# Check all record types
dig api.yourdomain.com ANY

# Trace the full resolution path
dig +trace api.yourdomain.com

# Check nameserver delegation
dig yourdomain.com NS

# Check TXT records (useful for ACM validation)
dig _acme-challenge.api.yourdomain.com TXT

Common issues and how to fix them:


  • “NXDOMAIN” (domain not found): Your nameservers are not delegated correctly. Check the NS records at your registrar.
  • Old IP address returned: DNS caching. Wait for the TTL to expire, or use dig @8.8.8.8 to check Google’s resolvers directly.
  • ACM validation stuck: The CNAME record name and value must match exactly what ACM expects. Check for trailing dots or typos.
  • Certificate not valid for domain: The certificate’s Common Name or SAN does not match the domain. Make sure you requested the certificate for the correct domain name.

A note about CloudFront (CDN)

So far we have connected users directly to our ALB through Route53. This works well, but for applications that serve static assets (images, CSS, JavaScript) or have users spread across the globe, you should consider putting CloudFront in front of your ALB.


CloudFront is AWS’s CDN (Content Delivery Network). It caches your content at edge locations around the world, so users get responses from a server that is geographically close to them instead of from your origin region.


Without CloudFront:
  User in Tokyo -> Route53 -> ALB in us-east-1 (200ms latency)

With CloudFront:
  User in Tokyo -> Route53 -> CloudFront edge in Tokyo (cached, 20ms)
                                    |
                                    v (cache miss only)
                              ALB in us-east-1

Benefits of CloudFront:


  • Lower latency: Content served from the nearest edge location.
  • Reduced origin load: Cached responses do not hit your ALB or ECS tasks.
  • DDoS protection: CloudFront integrates with AWS Shield for DDoS mitigation.
  • Free ACM certificates: CloudFront uses ACM certificates from us-east-1 (this is a requirement, the certificate must be in us-east-1 regardless of where your origin is).

We will not set up CloudFront in this article since it deserves its own deep dive, but keep it in mind for when you need to optimize performance for a global audience.


Cost breakdown

Let’s look at what this setup costs:


  • Route53 hosted zone: $0.50/month per hosted zone.
  • Route53 queries: $0.40 per million queries. For most applications this is negligible.
  • Route53 health checks: $0.50/month for a basic HTTPS health check. $1.00/month with latency measurement.
  • ACM certificates: Free when used with AWS services (ALB, CloudFront, API Gateway).
  • ALB: The ALB was already part of our ECS setup. No additional cost for HTTPS termination.

Total additional cost for DNS and TLS: roughly $1-2/month. This is one of the cheapest and highest value improvements you can make to your infrastructure.


Security best practices

Before we wrap up, here are some security practices to keep in mind:


  • Always redirect HTTP to HTTPS: Never serve production traffic over plain HTTP.
  • Use a modern TLS policy: ELBSecurityPolicy-TLS13-1-2-2021-06 or newer. Disable TLS 1.0 and 1.1.
  • Enable HSTS: Add the Strict-Transport-Security header in your application to tell browsers to always use HTTPS. This prevents downgrade attacks.
  • Use separate certificates per environment: Do not reuse production certificates in staging. ACM is free, so there is no reason not to have separate certificates.
  • Monitor certificate expiry: Even though ACM auto-renews, set up a CloudWatch alarm for certificate expiry as a safety net. If DNS validation fails for some reason, auto-renewal will fail silently.

Closing notes

Your application is now reachable at a real domain over HTTPS. We covered a lot of ground in this article: DNS fundamentals and record types, Route53 hosted zones and routing policies, TLS certificates with ACM, HTTPS termination at the ALB, HTTP to HTTPS redirects, health checks at both the ALB and DNS level, and the Terraform code to provision all of it.


This is a milestone in the series. Your TypeScript API is now running in containers on ECS, behind a load balancer, with auto-scaling, accessible at a clean URL over an encrypted connection. That is a production-grade setup.


In the next article, we will look at monitoring and observability so you can see what your application is doing in production and catch problems before your users do.


Hope you found this useful and enjoyed reading it, until next time!


Errata

If you spot any error or have any suggestion, please send me a message so it gets fixed.

Also, you can check the source code and changes in the sources here



$ Comments

Online: 0

Please sign in to be able to write comments.

2026-05-18 | Gabriel Garrido