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 Certificateresources
- 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 Challengeat a special URL:
http://woulf.fr/.well-known/acme-challenge/<token>
- It spins up a temporary acmesolverpod 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)