Setting up HTTPS certificates using Let’s Encrypt and cert-manager, with challenge configuration and security annotations in the Kubernetes Ingress.
In this fifth step, I’ll go over how I enabled an SSL/TLS certificate to secure woulf.fr, still within a DevOps mindset. We'll cover:
- Why Let’s Encrypt
- Why cert-manager
-
Essential annotations (
ssl-redirect
,hsts-max-age
)
Project context
Throughout the previous articles, I’ve:
- Deployed a Kubernetes cluster using MicroK8s on a VPS
- Containerized my application with Docker
- Set up a GitHub Actions CI/CD pipeline to automatically build and publish the Docker image
- Split app code and infrastructure into separate Git repositories
- Deployed everything to Kubernetes using a GitOps approach
The final step was to secure the site with HTTPS.
Why Let’s Encrypt?
Let’s Encrypt is a free, automated, and open certificate authority. It’s the go-to solution for obtaining a valid SSL/TLS certificate easily.
Advantages:
- Free: no cost for issuing or renewing certificates
- Automated: works well with DevOps tools and Kubernetes
- Trusted: certificates are valid in all modern browsers
Why cert-manager?
cert-manager is a Kubernetes operator built to automate certificate lifecycle management (issuing, renewal, rotation...).
It allows you to:
-
Create SSL/TLS certificates using Kubernetes
Certificate
resources - Handle validation challenges automatically with Let’s Encrypt
- Manage renewals with no manual intervention
It fits naturally into a GitOps workflow: certificates and their configuration can be versioned in the infra repo.
How the HTTP-01 challenge works
To verify domain ownership, Let’s Encrypt uses HTTP-based validation:
- cert-manager creates a
Challenge
at a special URL:
http://woulf.fr/.well-known/acme-challenge/<token>
- It spins up a temporary
acmesolver
pod that responds with a key - Let’s Encrypt queries the URL:
- If the correct key is returned, the certificate is issued
- If not, validation fails
By adding this annotation to the Ingress:
acme.cert-manager.io/http01-edit-in-place: "true"
the HTTP challenge is integrated directly into the main Ingress, avoiding conflicts (which I had with separate solver pods/Ingresses).
Adding annotations for extra security
nginx.ingress.kubernetes.io/ssl-redirect
nginx.ingress.kubernetes.io/ssl-redirect: "true"
Automatically redirects HTTP traffic to HTTPS.
Once the certificate is active, this ensures users always connect over HTTPS.
nginx.ingress.kubernetes.io/hsts-max-age
nginx.ingress.kubernetes.io/hsts-max-age: "31536000"
Adds an HSTS (HTTP Strict Transport Security) header, telling browsers to refuse HTTP for 1 year (31,536,000 seconds).
⚠️ Enable this only after HTTPS is confirmed working, as it prevents fallback to HTTP.
Final configuration example
Here’s a snippet of the final Ingress
configuration with all best practices included:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: woulf-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
acme.cert-manager.io/http01-edit-in-place: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/hsts-max-age: "31536000"
spec:
ingressClassName: nginx
tls:
- hosts:
- woulf.fr
secretName: woulf-fr-tls
rules:
- host: woulf.fr
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: portfolio
port:
number: 3000
What’s next?
✅ The site is now accessible via HTTPS, with a Let’s Encrypt certificate fully managed by cert-manager.
Here’s what I’m planning next:
- Monitoring: set up Prometheus / Grafana stack
- Centralized logging: using Loki or EFK
- Advanced GitOps: try out ArgoCD or Flux to continuously observe and reconcile cluster state
This project is my first step toward a fully automated and maintainable infrastructure. And it’s only the beginning 👨🚀
Top comments (0)