Skip to main content

Terminate TLS at the ingress

Inspect 1.36

TLS termination means the ingress controller holds the certificate, decrypts incoming HTTPS, and forwards plain HTTP to your pods inside the cluster. Your applications never handle certificates; the controller does so once for every application behind it.

How it works#

A client opens an HTTPS connection. The Hetzner load balancer runs at Layer 4, so it forwards the encrypted TCP stream to the ingress controller without decrypting it. The controller is where TLS ends: it presents the certificate, completes the handshake, and decrypts the request, then routes plain HTTP to the backend pod over the cluster network. The pod receives a normal HTTP request and responds as if no TLS were involved. This is the standard division of responsibility: the load balancer remains a plain TCP forwarder, and the reverse proxy holds the certificate and terminates TLS.

flowchart LR
  client[Client] -->|HTTPS| lb[Hetzner load balancer]
  lb -->|HTTPS| ctrl[Ingress controller<br/>holds the certificate]
  ctrl -->|plain HTTP| pod[Backend pod]

The certificate and its private key live in a Kubernetes Secret of type kubernetes.io/tls. The Ingress names that Secret, and the controller loads it. If you have not installed a controller yet, start with .

This is a reverse-proxy pattern, not an ingress-nginx feature. Any reverse proxy in front of your applications works the same way: Traefik reads the same kind of Secret, and Istio's ingress gateway (an Envoy proxy) terminates TLS by referencing a kubernetes.io/tls Secret from its Gateway resource. The resource you write changes, but the function is identical: hold the certificate, terminate TLS, forward plain traffic to the application.

Point an Ingress at a certificate Secret#

Add a tls block naming the hosts and the Secret that holds their certificate:

ingress.yamlyaml
		apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts: [my-app.example.com]
      secretName: my-app-tls
  rules:
    - host: my-app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app
                port:
                  number: 80
	

The nginx.ingress.kubernetes.io/ssl-redirect: "true" annotation redirects plain HTTP requests to HTTPS, so a caller on http:// is redirected to https://.

Where the certificate comes from#

Populate the Secret in one of two ways: create it manually from a certificate you purchased, or let cert-manager request and renew one for you.

If you purchased a certificate, create the Secret manually from the certificate and key files:

		$ kubectl create secret tls my-app-tls --cert=tls.crt --key=tls.key
	

You then renew it manually before it expires.

Automate the certificate and DNS record#

Putting a service on a name involves two recurring tasks: obtain a certificate for the name, and publish a DNS record that points the name to your load balancer. Automate both, and adding a hostname becomes the only manual step:

  • requests the certificate over ACME, writes it into the Secret above, and renews it before it expires.
  • reads the host from the Ingress and creates the matching DNS record at your provider, updating it if the load balancer address changes.

With both in place, you add a hostname to an Ingress and the certificate and the DNS record are created automatically.

Next#

For how Syself Autopilot handles secrets and encryption across the cluster, see .