If you’re exposing services to the internet through Kubernetes, securing them with HTTPS is essential. Luckily, you don’t need to manually manage SSL certificates anymore – with Let’s Encrypt and cert-manager, TLS certificates can be issued and renewed automatically.
In this post, we’ll walk you through securing your Kubernetes Ingress using Let’s Encrypt in production, without staging steps.
Prerequisites
Before starting, make sure you have:
- A working Kubernetes cluster
- A domain name (e.g.
app.example.com
) - DNS A/AAAA records pointing to your Ingress controller
- An Ingress controller like NGINX installed and configured
Step 1: Install cert-manager
We’ll install cert-manager
using Helm with CRDs included.
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set installCRDs=true
Verify the pods:
kubectl get pods -n cert-manager
Wait until all the cert-manager pods are running.
Step 2: Create a ClusterIssuer for Let’s Encrypt Production
Now we’ll set up a ClusterIssuer that tells cert-manager to use Let’s Encrypt’s production environment to issue certificates.
Create a file called clusterissuer-prod.yaml
:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: your-email@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
Replace
your-email@example.com
with your real email. This is important for recovery and expiration notices.
Apply it:
kubectl apply -f clusterissuer-prod.yaml
Step 3: Create a TLS-Enabled Ingress Resource
Now we’ll define an Ingress that:
- Uses the
letsencrypt-prod
ClusterIssuer - Automatically generates and attaches a TLS certificate
Create a file myapp-ingress.yaml
:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
namespace: default
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: myapp-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
Replace
app.example.com
andmyapp-service
with your actual domain and service.
Apply it:
kubectl apply -f myapp-ingress.yaml
cert-manager will detect the annotation and automatically request a certificate from Let’s Encrypt. The certificate will be saved to the Secret myapp-tls
.
Verify Certificate Issuance
Check the certificate:
kubectl describe certificate myapp-tls
Check the Ingress:
kubectl get ingress myapp-ingress
Visit your domain in the browser: https://app.example.com
You should see a valid Let’s Encrypt certificate in use.
Auto-Renewal and Maintenance
cert-manager monitors certificate expiration and will renew automatically 30 days before expiry.
To check certificate status:
kubectl get certificate --all-namespaces
To check logs:
kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager --follow
With cert-manager
and Let’s Encrypt, securing your Kubernetes workloads with HTTPS has never been easier. In just a few manifests, you can enable automatic TLS that renews itself with no human intervention – the way SSL should be.
Troubleshooting
If you encounter this error while applying your Ingress:
Error: admission webhook "validate.nginx.ingress.kubernetes.io" denied the request: host "abc" and path "/" is already defined in ingress
This means another Ingress resource in your cluster is already using the same host and path combination.
Solution 1: Remove the webhook (quick fix)
You can delete the webhook that enforces this validation:
kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission
Warning: This disables important safety checks for NGINX Ingress. Use with caution.
Solution 2: Upgrade NGINX Ingress Controller
This issue might be related to bugs in older versions of the NGINX Ingress controller. Consider upgrading to the latest stable version.
helm repo update
helm upgrade ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--set controller.image.tag=<latest-version>
Replace <latest-version>
with the latest available tag from the official chart.