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)