Lesson 19 of 32 · Level 3 — Application delivery
HTTPS & TLS termination
Serve your apps over HTTPS: where TLS can terminate (edge, passthrough, re-encrypt), a quick self-signed certificate, automated certificates with cert-manager (a private CA for labs, Let's Encrypt for real domains), HTTP-to-HTTPS redirects, and the annotations for passthrough and re-encryption with ingress-nginx.
Three places TLS can end
| Mode | Certificate lives on | Ingress sees HTTP? | Use when |
|---|---|---|---|
| Edge termination | Ingress controller | Yes | Most web apps (the default) |
| Passthrough | The pod | No (routes by SNI) | App must own TLS end to end |
| Re-encrypt | Ingress (public cert) + pod (internal cert) | Yes | Encryption required inside the cluster too |
A letter in a sealed envelope (TLS). With edge termination, the school office opens it at the front desk and carries the letter inside by hand. With passthrough, the office only reads the name on the envelope and delivers it still sealed to the classroom. With re-encrypt, the office opens it, checks it, and puts it in a new sealed school envelope for the trip down the corridor.
Example 1: a quick self-signed certificate (lab)
$ openssl req -x509 -nodes -newkey rsa:2048 -days 30 \
-keyout tls.key -out tls.crt \
-subj "/CN=podinfo.127.0.0.1.nip.io" \
-addext "subjectAltName=DNS:podinfo.127.0.0.1.nip.io"
$ kubectl create secret tls podinfo-tls --cert=tls.crt --key=tls.key -n shop-web
Add a tls section to the Ingress from lesson 17:
spec:
ingressClassName: nginx
tls:
- hosts: [ podinfo.127.0.0.1.nip.io ]
secretName: podinfo-tls
rules:
- host: podinfo.127.0.0.1.nip.io
# … same paths as before
$ curl -k https://podinfo.127.0.0.1.nip.io/ # -k: trust the self-signed cert for this test
$ curl -sI http://podinfo.127.0.0.1.nip.io/ | head -2
HTTP/1.1 308 Permanent Redirect # ingress-nginx redirects HTTP → HTTPS once TLS is set
Self-signed certificates are fine for learning; browsers warn about them, and you must renew them yourself.
Example 2: cert-manager with a lab CA
cert-manager automates issuing and renewing certificates.
$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml
$ kubectl -n cert-manager rollout status deploy/cert-manager-webhook
Create a private CA for the lab (a self-signed issuer signs a CA certificate, which then signs your app certificates):
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: lab-ca
namespace: cert-manager
spec:
isCA: true
commonName: lab-ca
secretName: lab-ca-keypair
issuerRef: { name: selfsigned, kind: ClusterIssuer }
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: lab-ca
spec:
ca:
secretName: lab-ca-keypair
Now just annotate the Ingress; cert-manager creates and renews the TLS Secret:
metadata:
name: podinfo
annotations:
cert-manager.io/cluster-issuer: lab-ca
spec:
tls:
- hosts: [ podinfo.127.0.0.1.nip.io ]
secretName: podinfo-tls-managed
$ kubectl get certificate -n shop-web
NAME READY SECRET AGE
podinfo-tls-managed True podinfo-tls-managed 30s
Example 3: Let's Encrypt for a real domain
For a public hostname pointing at your ingress, an ACME issuer with the HTTP-01 challenge:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory # switch to production when it works
email: you@example.com
privateKeySecretRef: { name: letsencrypt-staging-account }
solvers:
- http01:
ingress:
ingressClassName: nginx
Start with the staging endpoint (higher rate limits, untrusted certificates), then switch to production. HTTP-01 needs port 80 reachable from the internet; use DNS-01 for private clusters or wildcard certificates.
Passthrough and re-encrypt with ingress-nginx
# Passthrough: the pod terminates TLS (controller must run with --enable-ssl-passthrough)
metadata:
annotations:
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
---
# Re-encrypt: NGINX talks HTTPS to the pod
metadata:
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
For re-encrypt, the pods need their own certificates (cert-manager can issue them from an internal CA), and you can make NGINX verify them with the proxy-ssl-* annotations. Other controllers and Gateway API implementations have equivalent settings (lesson 20).
For the full picture (hardening TLS versions and ciphers, HSTS, mTLS, cert-manager in production), continue with Kubernetes Security & Hardening, lessons 02 and 07.
Try it: three certificates
- Add the self-signed certificate to the podinfo Ingress and test HTTPS and the HTTP→HTTPS redirect.
- Install cert-manager, create the lab CA and switch the Ingress to the annotation; inspect the issued certificate (
kubectl get secret podinfo-tls-managed -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -text | head -15). - Delete the managed Secret and watch cert-manager recreate it.
- If you have a public domain and a reachable cluster, get a Let's Encrypt staging certificate.
- Write down, for one of your real apps, which TLS mode you'd choose and why.
Going deeper: TLS operations
- Alert on certificate expiry (cert-manager exposes metrics) even with auto-renewal: renewals can fail silently (DNS changes, rate limits).
- Keep private keys out of Git; let cert-manager or a secret manager own them.
- Standardise on one issuer per purpose (public ACME, internal CA) and document which Ingresses use which.
Recap
- TLS can end at the edge (ingress), be passed through to the pod, or be re-encrypted.
- Lab: self-signed cert +
kubernetes.io/tlsSecret; real: cert-manager with a CA issuer or Let's Encrypt (staging first). - ingress-nginx redirects HTTP→HTTPS when TLS is set; annotations enable passthrough and re-encrypt.
- Go further in Kubernetes Security & Hardening (lessons 02 and 07).
This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.