| title | Cert-manager and Let's Encrypt with Application Gateway for Containers - Gateway API |
|---|---|
| description | Learn how to configure Application Gateway for Containers with certificates managed by CNCF project cert-manager. |
| services | application-gateway |
| author | mbender-ms |
| ms.service | azure-appgw-for-containers |
| ms.topic | how-to |
| ms.date | 2/20/2026 |
| ms.author | mbender |
This guide demonstrates how to use cert-manager to automatically issue and renew SSL/TLS certificates to one or more frontends of your Azure Application Gateway for Containers deployment. We use the Gateway API to configure the necessary resources.
For the purposes of this example, we have cert-manager configure certificates issued from Let's Encrypt to demonstrate an end-to-end deployment, where Application Gateway for Containers is providing TLS offloading.
For certificates to be issued by Let's Encrypt, a challenge is required by the authority to validate domain ownership. This validation happens by allowing cert-manager to create a pod and HTTPRoute resource that exposes an endpoint during certificate issuance, proving your ownership of the domain name.
More details on cert-manager and Let's Encrypt with AKS in general may be found here.
-
If following the BYO deployment strategy, ensure that you set up your Application Gateway for Containers resources and ALB Controller (Add-on or Helm)
-
If following the ALB managed deployment strategy, ensure that you provision your ALB Controller (Add-on or Helm) and the Application Gateway for Containers resources via the ApplicationLoadBalancer custom resource.
-
Deploy sample HTTP application Apply the following deployment.yaml file on your cluster to create a sample web application to demonstrate the header rewrite.
kubectl apply -f https://raw.githubusercontent.com/MicrosoftDocs/azure-docs/refs/heads/main/articles/application-gateway/for-containers/examples/traffic-split-scenario/deployment.yaml
This command creates the following on your cluster:
- a namespace called
test-infra - two services called
backend-v1andbackend-v2in thetest-infranamespace - two deployments called
backend-v1andbackend-v2in thetest-infranamespace
- a namespace called
Create a new Gateway resource that listens for HTTP requests from Let's Encrypt during the challenge process.
Create a gateway:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway-01
namespace: test-infra
annotations:
alb.networking.azure.io/alb-namespace: alb-test-infra
alb.networking.azure.io/alb-name: alb-test
cert-manager.io/issuer: letsencrypt-prod
spec:
gatewayClassName: azure-alb-external
listeners:
- name: http-listener
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
EOF[!INCLUDE application-gateway-for-containers-frontend-naming]
- Set the following environment variables
RESOURCE_GROUP='<resource group name of the Application Gateway For Containers resource>'
RESOURCE_NAME='alb-test'
RESOURCE_ID=$(az network alb show --resource-group $RESOURCE_GROUP --name $RESOURCE_NAME --query id -o tsv)
FRONTEND_NAME='frontend'- Create a Gateway
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway-01
namespace: test-infra
annotations:
alb.networking.azure.io/alb-id: $RESOURCE_ID
cert-manager.io/issuer: letsencrypt-prod
spec:
gatewayClassName: azure-alb-external
listeners:
- name: http-listener
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
addresses:
- type: alb.networking.azure.io/alb-frontend
value: $FRONTEND_NAME
EOFOnce the gateway resource is created, ensure the status is valid, the listener is Programmed, and an address is assigned to the gateway.
kubectl get gateway gateway-01 -n test-infra -o yamlExample output of successful gateway creation.
status:
addresses:
- type: IPAddress
value: xxxx.yyyy.alb.azure.com
conditions:
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: Valid Gateway
observedGeneration: 1
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: Application Gateway For Containers resource has been successfully updated.
observedGeneration: 1
reason: Programmed
status: "True"
type: Programmed
listeners:
- attachedRoutes: 0
conditions:
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: ""
observedGeneration: 1
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: Listener is accepted
observedGeneration: 1
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: Application Gateway For Containers resource has been successfully updated.
observedGeneration: 1
reason: Programmed
status: "True"
type: Programmed
name: https-listener
supportedKinds:
- group: gateway.networking.k8s.io
kind: HTTPRouteInstall cert-manager using Helm:
helm install \
cert-manager oci://quay.io/jetstack/charts/cert-manager \
--version v1.19.3 \
--namespace cert-manager \
--create-namespace \
--set config.enableGatewayAPI=true \
--set crds.enabled=trueThe helm installation creates three deployments and some services and pods in a new namespace called cert-manager. It also installs cluster-scoped supporting resources, such as RBAC roles and Custom Resource Definitions.
Create a ClusterIssuer resource to define how cert-manager will communicate with Let's Encrypt. For this example, an HTTP challenge is used. During challenge, cert-manager creates an HTTPRoute resource and corresponding pod presenting a validation endpoint to prove ownership of the domain.
Tip
Other challenges supported by Let's Encrypt are documented on letsencrypt.org - Challenge Types
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
namespace: test-infra
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory # production endpoint
#server: https://acme-staging-v02.api.letsencrypt.org/directory # staging endpoint
email: [email protected]
privateKeySecretRef:
name: letsencrypt-private-key
solvers:
- http01:
gatewayHTTPRoute:
parentRefs:
- name: gateway-01
namespace: test-infra
kind: Gateway
EOFVerify the resource was created
kubectl get ClusterIssuer -A -o yamlThe status should show True and type Ready.
status:
acme:
lastPrivateKeyHash: x+xxxxxxxxxxxxxxxxxxxxxxx+MY4PAEeotr9XH3V7I=
lastRegisteredEmail: [email protected]
uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/165888253
conditions:
- lastTransitionTime: "2024-10-04T21:22:40Z"
message: The ACME account was registered with the ACME server
observedGeneration: 1
reason: ACMEAccountRegistered
status: "True"
type: Ready
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: letsencrypt-cert
namespace: test-infra
spec:
secretName: letsencrypt-secret # name published to secret store
issuerRef:
name: letsencrypt-prod # ClusterIssuer resource name
kind: ClusterIssuer
dnsNames:
- contoso.com # domain name to be used
EOFExecute the following command to validate issuance of the certificate. If the certificate has been issued, the value of the READY column should be True.
kubectl get certificate letsencrypt-cert -n test-infraIf the certificate wasn't issued, you can execute the following command to validate the status of a challenge.
Note
If the certificate has been successfully issued, the challenge will no longer be listed.
kubectl get challenges -n test-infra -o yamlModify the gateway to add a second listener to terminate HTTPS requests with the issued Let's Encrypt certificate.
Create a gateway:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway-01
namespace: test-infra
annotations:
alb.networking.azure.io/alb-namespace: alb-test-infra
alb.networking.azure.io/alb-name: alb-test
cert-manager.io/issuer: letsencrypt-cert
spec:
gatewayClassName: azure-alb-external
listeners:
- name: http-listener
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
- name: https-listener
port: 443
protocol: HTTPS
tls:
certificateRefs:
- name: letsencrypt-secret
allowedRoutes:
namespaces:
from: Same
EOF[!INCLUDE application-gateway-for-containers-frontend-naming]
- Set the following environment variables
RESOURCE_GROUP='<resource group name of the Application Gateway For Containers resource>'
RESOURCE_NAME='alb-test'
RESOURCE_ID=$(az network alb show --resource-group $RESOURCE_GROUP --name $RESOURCE_NAME --query id -o tsv)
FRONTEND_NAME='frontend'- Create a Gateway
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway-01
namespace: test-infra
annotations:
alb.networking.azure.io/alb-id: $RESOURCE_ID
cert-manager.io/issuer: letsencrypt-prod
spec:
gatewayClassName: azure-alb-external
listeners:
- name: http-listener
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
- name: https-listener
port: 443
protocol: HTTPS
tls:
certificateRefs:
- name: letsencrypt-secret
allowedRoutes:
namespaces:
from: Same
addresses:
- type: alb.networking.azure.io/alb-frontend
value: $FRONTEND_NAME
EOFCreate an HTTPRoute to handle requests received by the https-listener listener.
Important
Ensure you replace contoso.com with the domain name you are expecting the certificate to be issued to.
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: https-example
namespace: test-infra
spec:
parentRefs:
- name: gateway-01
hostnames:
- "contoso.com"
rules:
- backendRefs:
- name: backend-v1
port: 8080
EOFOnce the HTTPRoute resource is created, ensure the route is Accepted and the Application Gateway for Containers resource is Programmed.
kubectl get httproute cert-manager-route -n test-infra -o yamlVerify the status of the Application Gateway for Containers resource is successfully updated.
status:
parents:
- conditions:
- lastTransitionTime: "2023-06-19T22:18:23Z"
message: ""
observedGeneration: 1
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
- lastTransitionTime: "2023-06-19T22:18:23Z"
message: Route is Accepted
observedGeneration: 1
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2023-06-19T22:18:23Z"
message: Application Gateway For Containers resource has been successfully updated.
observedGeneration: 1
reason: Programmed
status: "True"
type: Programmed
controllerName: alb.networking.azure.io/alb-controller
parentRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-01
namespace: test-infraNow we're ready to send some traffic to our sample application, via the hostname used for your certificate.
Important
Ensure you replace contoso.com with the domain name you're expecting the certificate to be issued to.
curl https://contoso.com/ -v 2>&1 | grep issuerYou should see the following output:
* issuer: C=US; O=Let's Encrypt; CN=R10
Congratulations, you have installed ALB Controller, deployed a backend application, issued a certificate from Let's Encrypt with cert-manager, and routed traffic to the application via Application Gateway for Containers.