Cert-Manager with Let's Encrypt & Route53 DNS-01

Cert-Manager is an open-source X.509 certificate management controller for Kubernetes. It automatically provisions, validates, and renews TLS certificates from public Certificate Authorities like Let's Encrypt or private CAs before expiration.
This guide demonstrates how to configure Cert-Manager on Amazon EKS using Route53 DNS-01 challenge validation, EKS Pod Identity, and automatic TLS termination with Gateway API / Ingress.
📺 Video Tutorial
Architecture: DNS-01 ACME Challenge with Route53
sequenceDiagram
participant Gateway as Gateway / Ingress
participant CM as Cert-Manager Controller
participant LE as Let's Encrypt ACME Server
participant R53 as AWS Route53
Gateway->>CM: Request Certificate (Annotation)
CM->>LE: Initiate Order & DNS-01 Challenge
LE-->>CM: Challenge Token & TXT Record Key
CM->>R53: Create _acme-challenge TXT Record (via Pod Identity)
LE->>R53: Query & Verify TXT Record
LE-->>CM: Issue Signed Certificate & Chain
CM->>Gateway: Store in Kubernetes TLS Secret
CM->>R53: Clean up TXT Record
Step 1. Configure IAM Policy & EKS Pod Identity
Cert-Manager needs permissions to create and remove _acme-challenge TXT records in Route53.
cert-manager-iam.tf
# 1. IAM Policy for Route53 DNS-01 Challenges
resource "aws_iam_policy" "cert_manager_dns" {
name = "CertManagerRoute53Policy-${module.eks.cluster_name}"
description = "Allows Cert-Manager to manage DNS-01 ACME challenge TXT records in Route53"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = "route53:GetChange"
Resource = "arn:aws:route53:::change/*"
},
{
Effect = "Allow"
Action = [
"route53:ListHostedZones",
"route53:ListHostedZonesByName",
"route53:ListResourceRecordSets"
]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"route53:ChangeResourceRecordSets"
]
Resource = "arn:aws:route53:::hostedzone/*"
}
]
})
}
# 2. IAM Role with Pod Identity Trust
data "aws_iam_policy_document" "pod_id_trust" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["pods.eks.amazonaws.com"]
}
actions = ["sts:AssumeRole", "sts:TagSession"]
}
}
resource "aws_iam_role" "cert_manager" {
name = "cert-manager-role-${module.eks.cluster_name}"
assume_role_policy = data.aws_iam_policy_document.pod_id_trust.json
}
resource "aws_iam_role_policy_attachment" "cert_manager" {
role = aws_iam_role.cert_manager.name
policy_arn = aws_iam_policy.cert_manager_dns.arn
}
# 3. Pod Identity Association
resource "aws_eks_pod_identity_association" "cert_manager" {
cluster_name = module.eks.cluster_name
namespace = "cert-manager"
service_account = "cert-manager"
role_arn = aws_iam_role.cert_manager.arn
}
Step 2. Install Cert-Manager via Helm
cert-manager-values.yaml
installCRDs: true
config:
apiVersion: controller.config.cert-manager.io/v1alpha1
kind: ControllerConfiguration
enableGatewayAPI: true
serviceAccount:
create: true
name: "cert-manager"
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.16.2 \
-f cert-manager-values.yaml
Step 3. Create Let's Encrypt ClusterIssuer
cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-route53
spec:
acme:
email: admin@vettom.online
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-route53-account-key
solvers:
- selector:
dnsZones:
- vettom.online
- "*.vettom.online"
dns01:
route53:
region: eu-west-1
Step 4. Attach TLS Certificate to Gateway API or Ingress
gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: external-gateway
namespace: gateway
annotations:
cert-manager.io/cluster-issuer: letsencrypt-route53
spec:
gatewayClassName: external-gatewayclass
listeners:
- name: https
hostname: "*.vettom.online"
port: 443
protocol: HTTPS
allowedRoutes:
namespaces:
from: All
tls:
certificateRefs:
- kind: Secret
group: ""
name: "wildcard-vettom-cert"
namespace: gateway