Kubernetes Administration — Level by Level›19 · HTTPS & TLS termination

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.

Practitioner
Key wordsHTTPSTLS terminationkubernetes.io/tls Secretcert-managerClusterIssuerLet's EncryptACME HTTP-01ssl-redirectSSL passthroughre-encryptbackend-protocol
Edge termination Client Ingress Pod TLS cert on ingress plain HTTP inside Passthrough Client Ingress Pod TLS (SNI only) same TLS to pod Re-encrypt Client Ingress Pod TLS cert on ingress new TLS (internal CA) thick line = encrypted · thin line = plain HTTP
Edge termination, passthrough and re-encryption compared.

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

  1. Add the self-signed certificate to the podinfo Ingress and test HTTPS and the HTTP→HTTPS redirect.
  2. 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).
  3. Delete the managed Secret and watch cert-manager recreate it.
  4. If you have a public domain and a reachable cluster, get a Let's Encrypt staging certificate.
  5. 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/tls Secret; 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.