TLS Certificates in Kubernetes: Complete Guide

How to configure TLS in Kubernetes using Secrets, Ingress termination, and cert-manager. Includes step-by-step instructions for using getacert certificates in your cluster.


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[].hosts list must match the certificate's Subject Alternative Names (SANs).
  • secretName references 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.


More in Guides