Why TLS in Kubernetes Matters
Every production Kubernetes cluster needs TLS. External traffic hitting your Ingress controller, internal service-to-service communication, webhooks calling the API server — all of it should be encrypted. Kubernetes has first-class support for TLS through Secrets and Ingress resources, but the setup has enough moving parts to trip up even experienced engineers.
This guide covers the three main approaches: manual TLS Secrets, cert-manager automation, and using externally generated certificates (like those from getacert.com).
Kubernetes TLS Secrets
A TLS Secret is a standard Kubernetes Secret with type kubernetes.io/tls. It holds two keys: tls.crt (the certificate chain) and tls.key (the private key).
Creating a TLS Secret from Files
If you already have a certificate and key — say, generated from getaCert.com — create the Secret directly:
kubectl create secret tls my-app-tls \
--cert=certificate.crt \
--key=private.key \
-n my-namespace
Creating a TLS Secret from a YAML Manifest
For GitOps workflows, define the Secret declaratively. The certificate and key must be base64-encoded:
apiVersion: v1
kind: Secret
metadata:
name: my-app-tls
namespace: my-namespace
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded-cert>
tls.key: <base64-encoded-key>
Encode your files:
cat certificate.crt | base64 -w0
cat private.key | base64 -w0
Verifying a TLS Secret
Confirm the Secret exists and contains valid data:
kubectl get secret my-app-tls -n my-namespace -o yaml
# Decode and inspect the certificate
kubectl get secret my-app-tls -n my-namespace \
-o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout
Ingress TLS Termination
The most common use of TLS Secrets is terminating HTTPS at the Ingress controller. The Ingress resource references a TLS Secret, and the controller (nginx, Traefik, HAProxy, etc.) handles the handshake.
Basic Ingress with TLS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
namespace: my-namespace
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- app.example.com
secretName: my-app-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 80
Key points:
- The
tls[].hostslist must match the certificate's Subject Alternative Names (SANs). secretNamereferences the TLS Secret in the same namespace.- Traffic between the Ingress controller and your pods is unencrypted by default (terminated at the edge). For end-to-end encryption, configure backend TLS on the Ingress controller.
Multiple TLS Hosts
A single Ingress can terminate TLS for multiple domains using SNI:
spec:
tls:
- hosts:
- app.example.com
secretName: app-tls
- hosts:
- api.example.com
secretName: api-tls
Automating Certificates with cert-manager
For production clusters, manually creating and rotating TLS Secrets does not scale. cert-manager is the standard solution. It watches for Certificate resources, requests certs from a configured issuer (Let's Encrypt, Vault, Venafi, or a custom CA), and stores them as Kubernetes Secrets.
Installing cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.0/cert-manager.yaml
# Verify the installation
kubectl get pods -n cert-manager
Creating a ClusterIssuer for Let's Encrypt
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
Requesting a Certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-cert
namespace: my-namespace
spec:
secretName: my-app-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- app.example.com
- www.app.example.com
cert-manager creates the TLS Secret automatically and renews it before expiry.
Ingress Annotations Shortcut
Instead of creating a Certificate resource explicitly, annotate your Ingress:
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
cert-manager detects the annotation and issues a certificate for the hosts listed in spec.tls.
Using getacert Certificates in Kubernetes
For development clusters, staging environments, or internal services that don't need publicly trusted certificates, generating a cert from getaCert.com is the fastest path.
Step 1: Generate the Certificate
Go to getaCert.com/casign and fill in your details:
- Common Name:
app.dev.internal(or your internal hostname) - Subject Alternative Names: add any additional hostnames
- Validity: choose your duration
Download the certificate (certificate.crt) and private key (private.key).
Step 2: Create the Kubernetes Secret
kubectl create secret tls dev-app-tls \
--cert=certificate.crt \
--key=private.key \
-n development
Step 3: Configure the Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: dev-app-ingress
namespace: development
spec:
tls:
- hosts:
- app.dev.internal
secretName: dev-app-tls
rules:
- host: app.dev.internal
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dev-app
port:
number: 8080
Step 4: Trust the CA in Your Cluster (Optional)
If other services in the cluster need to trust your getacert CA-signed certificate, distribute the CA certificate as a ConfigMap and mount it into pods:
apiVersion: v1
kind: ConfigMap
metadata:
name: custom-ca
namespace: development
data:
ca.crt: |
-----BEGIN CERTIFICATE-----
<your CA certificate content>
-----END CERTIFICATE-----
Mount it in your pod spec:
volumes:
- name: ca-certs
configMap:
name: custom-ca
volumeMounts:
- name: ca-certs
mountPath: /etc/ssl/certs/custom-ca.crt
subPath: ca.crt
Troubleshooting
Certificate not showing up
# Check if the Secret exists
kubectl get secret -n my-namespace | grep tls
# Check Ingress events
kubectl describe ingress my-app-ingress -n my-namespace
# Check cert-manager logs (if using cert-manager)
kubectl logs -n cert-manager -l app=cert-manager
Browser shows "untrusted certificate"
For self-signed or private CA certs, this is expected. Import the CA certificate into your browser or OS trust store, or use curl -k for testing.
SAN mismatch errors
The hostname you connect to must appear in the certificate's SAN list. Check with:
kubectl get secret my-app-tls -n my-namespace \
-o jsonpath='{.data.tls\.crt}' | base64 -d | \
openssl x509 -noout -ext subjectAltName
Summary
| Approach | Best For | Automation | Trusted |
|---|---|---|---|
| Manual TLS Secret | Dev/test, quick setup | None | Depends on CA |
| cert-manager + Let's Encrypt | Production public-facing | Full | Yes |
| cert-manager + Vault/Custom CA | Internal services | Full | Internal only |
| getacert + manual Secret | Dev/staging, fast iteration | None | Internal only |
For production public services, use cert-manager with Let's Encrypt. For everything else — dev clusters, staging, internal services, quick prototypes — generate a certificate at getaCert.com and create the Secret in under a minute.