Tuesday, May 26, 2026Tech HubAboutContactAdvertiseNewsletter
Back to Home

                     How to Encrypt Kubernetes Traffic with cert-manager, Let's Encrypt, and Internal TLS

How to Encrypt Kubernetes Traffic with cert-manager, Let's Encrypt, and Internal TLS

Most engineers assume their Kubernetes cluster encrypts all of its traffic. It doesn't. The commands you run with kubectl are encrypted — your client and the API server speak TLS. The API server talki

B
Blizine Admin
·1 min read·0 views

How to Encrypt Kubernetes Traffic with cert-manager, Let's Encrypt, and Internal TLS

Destiny Erhabor

Most engineers assume their Kubernetes cluster encrypts all of its traffic. It doesn't. The commands you run with kubectl are encrypted — your client and the API server speak TLS. The API server talking to etcd is usually encrypted too, depending on how the cluster was provisioned. But traffic between your pods? Plaintext by default. Ingress traffic from the internet to your services? Only encrypted if you explicitly configure TLS. And certificates for internal services? You have to provision those yourself. This is not a Kubernetes oversight. It's a deliberate design choice — Kubernetes provides the primitives and leaves the implementation to you. The problem is that certificate management is notoriously painful. Certificates expire. Provisioning them manually doesn't scale. Forgetting to rotate them causes outages. cert-manager solves this. It runs as a controller inside your cluster, watches for Certificate resources, requests certificates from configured issuers, stores them in Kubernetes Secrets, and rotates them automatically before they expire. You declare what you want, cert-manager makes it happen and keeps it that way. In this article you'll work through how cert-manager's core model works, automate public Ingress TLS using Let's Encrypt, set up an internal Certificate Authority for service-to-service encryption, and understand how certificate rotation works so outages caused by expired certificates become a thing of the past. Prerequisites

A kind cluster with the nginx Ingress controller installed

Helm 3 installed

A domain name with DNS you control — needed for the Let's Encrypt demo

Basic understanding of TLS: you know what a certificate, a private key, and a CA are

All demo files are in the DevOps-Cloud-Projects GitHub repository. Table of Contents

What Is and Isn't Encrypted in Kubernetes

How cert-manager Works

The Four Core Resources

Issuers and ClusterIssuers

The Certificate Lifecycle

ACME Challenges: HTTP-01 vs DNS-01

Demo 1 — Install cert-manager and Issue a Let's Encrypt Certificate

How to Get a Wildcard Certificate with DNS-01

Demo 2 — Set Up an Internal CA for Service-to-Service TLS

How Certificate Rotation Works

Cleanup

Conclusion

What Is and Isn't Encrypted in Kubernetes? Before installing anything, it's worth being precise about what the cluster already protects and what it leaves open.

Traffic path Encrypted by default? Notes

kubectl → API server Yes TLS with the cluster CA

API server → etcd Usually Depends on cluster provisioner — verify with your setup

API server → kubelet Yes TLS, but kubelet cert verification depends on configuration

Pod → Pod (same cluster) No Plaintext unless you add a service mesh or mTLS

Internet → Ingress No Opt-in — requires TLS configuration on the Ingress resource

Pod → Kubernetes API Yes Via the service account token and cluster CA

The two gaps that matter most in practice are pod-to-pod traffic and Ingress TLS. This article covers both Ingress TLS with Let's Encrypt and internal service-to-service encryption using a private CA. How cert-manager Works cert-manager is a Kubernetes operator. It extends the Kubernetes API with custom resources that represent certificate requests and their configuration. When you create a Certificate resource, cert-manager's controller picks it up, requests a certificate from the configured issuer, and stores the resulting certificate and private key in a Kubernetes Secret. When the certificate approaches its expiry, cert-manager renews it automatically. This model means your application doesn't know or care about certificate management. It reads a Secret. cert-manager keeps that Secret fresh. The Four Core Resources cert-manager introduces four custom resources that you'll use regularly:

Resource What it represents

Issuer A certificate authority or ACME account — namespace-scoped

ClusterIssuer Same as Issuer, but available cluster-wide

Certificate A request for a certificate — describes what you want

CertificateRequest An individual signing request — created automatically by cert-manager, rarely touched directly

In practice you'll mostly deal with ClusterIssuer and Certificate. The ClusterIssuer defines where certificates come from. The Certificate defines what certificate you want and where to store it. Issuers and ClusterIssuers An Issuer can only issue certificates within its own namespace. A ClusterIssuer can issue certificates in any namespace. For shared infrastructure like Let's Encrypt, you almost always want a ClusterIssuer. For application-specific internal CAs, an Issuer scoped to that application's namespace is the safer choice. cert-manager supports several issuer types. The three you'll encounter most often are: ACME — for public certificates from Let's Encrypt or any ACME-compatible CA. Ownership of the domain is proven via an HTTP-01 or DNS-01 challenge. CA — for internal certificates signed by a CA whose private key is stored in a Kubernetes Secret. Used for service-to-service TLS within the cluster. Self-signed — generates self-signed certificates. Rarely useful on its own, but essential as the bootstrap step when creating an internal CA. The Certificate Lifecycle When you create a Certificate resource, cert-manager follows this sequence:

Creates a CertificateRequest with a CSR (Certificate Signing Request)

Passes the CSR to the configured issuer

For ACME issuers: creates a Challenge resource and fulfils it (more on this below)

Receives the signed certificate from the issuer

Stores the certificate and private key in the Kubernetes Secret named in spec.secretName

Monitors the certificate's expiry — by default, renews when 2/3 of the validity period has elapsed

Your application mounts the Secret. cert-manager updates it silently. Most applications that watch for file changes will pick up the new certificate without a restart. ACME Challenges: HTTP-01 vs DNS-01 Let's Encrypt needs proof that you control the domain before it issues a certificate. ACME defines two challenge types for this. HTTP-01 works by having cert-manager create a temporary HTTP endpoint at http:///.well-known/acme-challenge/. Let's Encrypt sends a request to that URL. If the response matches the expected token, the challenge passes. This requires your cluster to be reachable from the internet on port 80. DNS-01 works by having cert-manager create a temporary DNS TXT record at _acme-challenge.. Let's Encrypt checks for that record. This doesn't require inbound HTTP access, which makes it the right choice for private clusters, and it's the only way to get wildcard certificates (*.example.com). The trade-off: HTTP-01 is simpler to set up but only works for single domains and requires internet-accessible infrastructure. DNS-01 requires API access to your DNS provider but works for internal clusters and wildcards. Demo 1 — Install cert-manager and Issue a Certificate Using Pebble and Let's Encrypt Pebble is Let's Encrypt's local ACME test server. It runs inside your cluster, issues certificates using the same ACME protocol as Let's Encrypt, and requires no public domain or internet access. Using Pebble lets you test the full cert-manager flow — challenge, issuance, renewal — on a plain kind cluster. Once you understand the flow locally, switching to real Let's Encrypt is a one-line change: replace the ClusterIssuer server URL and point a DNS record at a publicly reachable cluster. The rest of the configuration is identical. You'll install cert-manager, create a ClusterIssuer for Let's Encrypt, deploy a sample application with an Ingress, and watch a real certificate be issued and stored automatically. Step 1: Install cert-manager cert-manager is now distributed via OCI Helm charts from quay.io/jetstack. The --set crds.enabled=true flag installs the Custom Resource Definitions as part of the chart: helm upgrade cert-manager oci://quay.io/jetstack/charts/cert-manager \ --install \ --create-namespace \ --namespace cert-manager \ --set crds.enabled=true \ --version v1.17.0 \ --wait

You also need the nginx Ingress controller — cert-manager routes HTTP-01 challenges through it. The controller.service.type=ClusterIP override is for kind specifically: the default LoadBalancer Service never gets an EXTERNAL-IP on kind (there's no cloud LB), which makes --wait hang forever. On a real cluster, drop the override and keep LoadBalancer. helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update

helm install ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx \ --create-namespace \ --set controller.service.type=ClusterIP \ --wait

Confirm all four components are running: kubectl get pods -n cert-manager kubectl get pods -n ingress-nginx

NAME READY STATUS RESTARTS AGE cert-manager-76f84784c8-r4fx4 1/1 Running 0 6m45s cert-manager-cainjector-66fbf49587-gv25n 1/1 Running 0 6m45s cert-manager-webhook-577fddf86-l5wj4 1/1 Running 0 6m45s

NAME READY STATUS RESTARTS AGE ingress-nginx-controller-6c7cd85885-h7zgx 1/1 Running 0 3m34s

kind-specific gotcha — remove the nginx admission webhook now.** On kind, the nginx admission webhook serves with a self-signed certificate that the Kubernetes API server cannot verify. The first time you try to create any Ingress resource you'll see failed calling webhook "validate.nginx.ingress.kubernetes.io": ... x509: certificate signed by unknown authority. Delete the

📰Originally published at freecodecamp.org

Comments