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. ```mermaid flowchart LR client[Client] -->|HTTPS| lb[Hetzner load balancer] lb -->|HTTPS| ctrl[Ingress controller
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 [Install Traefik](/docs/hetzner/apalla/network/expose/install-traefik). 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: ```yaml title="ingress.yaml" 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: ```console $ kubectl create secret tls my-app-tls --cert=tls.crt --key=tls.key ``` You then renew it manually before it expires. The usual setup delegates renewal to [cert-manager](/docs/hetzner/apalla/network/dns-certs/install-cert-manager): it requests the certificate from a certificate authority, writes it into the Secret, and renews it automatically. Install cert-manager, create a [Let's Encrypt ClusterIssuer](/docs/hetzner/apalla/network/dns-certs/lets-encrypt-clusterissuer) (a cluster-wide object that tells cert-manager which certificate authority to use and how to prove domain ownership), and add `cert-manager.io/cluster-issuer: ` to the `Ingress` annotations. Syself Autopilot does not provide cert-manager; you install it yourself. ## 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: - [cert-manager](/docs/hetzner/apalla/network/dns-certs/install-cert-manager) requests the certificate over ACME, writes it into the Secret above, and renews it before it expires. - [external-dns](/docs/hetzner/apalla/network/dns-certs/automatic-dns-with-external-dns) 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. Terminating at the ingress leaves the last hop, controller to pod, as plain HTTP inside the cluster. For most workloads this is acceptable, because the pod network is not exposed. If a compliance rule requires encryption all the way to the pod, you can re-encrypt: the controller terminates the client's TLS, then opens a second TLS connection to the backend. In ingress-nginx this is the `nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"` annotation, and the backend pod must serve HTTPS itself. A service mesh with mTLS is an alternative way to encrypt that hop; see [Choose a service mesh](/docs/hetzner/apalla/network/mesh/choose-a-service-mesh). ## Next - [Set up a Let's Encrypt ClusterIssuer](/docs/hetzner/apalla/network/dns-certs/lets-encrypt-clusterissuer) to issue and renew certificates automatically. - [Serve multiple domains and wildcards](/docs/hetzner/apalla/network/expose/multi-domain-and-wildcard) to hold a certificate per host behind one controller. For how Syself Autopilot handles secrets and encryption across the cluster, see [security](/docs/hetzner/apalla/concepts/ownership/security-model).