Kubernetes TLS secrets

To create a Kubernetes TLS secret, run `kubectl create secret tls example-com-tls --cert=fullchain.crt --key=private.key`. The `--cert` file must contain the **full chain** — your certificate followed by its intermediates — because Ingress controllers serve whatever is in that file verbatim and do not fetch missing links. The secret type is `kubernetes.io/tls`, and the two keys inside are always named `tls.crt` and `tls.key` regardless of your filenames.

Updated August 19, 2026 Verified against OpenSSL 3.5.7

Step 1 — build the chain #

cat certificate.crt intermediate.crt > fullchain.crt

Step 2 — create the secret #

kubectl create secret tls example-com-tls \
--cert=fullchain.crt \
--key=private.key \
--namespace=production

Idempotently, so it can live in a script:

kubectl create secret tls example-com-tls \
--cert=fullchain.crt --key=private.key \
--namespace=production \
--dry-run=client -o yaml | kubectl apply -f -

Step 3 — reference it from the Ingress #

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-com
namespace: production
spec:
ingressClassName: nginx
tls:
  - hosts:
      - example.com
      - www.example.com
    secretName: example-com-tls
rules:
  - host: example.com
    http:
      paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: web
              port:
                number: 80

Writing the secret as YAML directly #

The keys are always tls.crt and tls.key, whatever your files were called:

Base64 the inputs
base64 -w0 fullchain.crt
base64 -w0 private.key
apiVersion: v1
kind: Secret
metadata:
name: example-com-tls
namespace: production
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...

Inspecting an existing secret #

Read the certificate out of a secret
kubectl get secret example-com-tls -n production \
-o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -subject -dates -ext subjectAltName
How many certificates are in the chain?
kubectl get secret example-com-tls -n production \
-o jsonpath='{.data.tls\.crt}' | base64 -d | grep -c "BEGIN CERTIFICATE"
Expected output
2

A count of 1 means the chain is incomplete.

Checking the key matches before applying #

Worth doing in CI. A mismatched pair produces an Ingress that fails to serve and an error message that does not point here:

[ "$(openssl pkey -in private.key -pubout | openssl sha256)" \
= "$(openssl x509 -in fullchain.crt -noout -pubkey | openssl sha256)" ] \
|| { echo "key/cert mismatch"; exit 1; }

cert-manager #

For anything ongoing, cert-manager handles issuance and renewal and writes the secret for you:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com
namespace: production
spec:
secretName: example-com-tls
dnsNames:
  - example.com
  - www.example.com
issuerRef:
  name: letsencrypt-prod
  kind: ClusterIssuer

The resulting secret has the same shape, so the Ingress above needs no changes.

Frequently asked questions

Does the secret need the full chain?
Yes. Ingress controllers serve the contents of tls.crt exactly as given. If it holds only the leaf, clients cannot build a path to a trusted root and verification fails outside browsers.
Are the contents encrypted?
No. Kubernetes Secrets are base64-encoded, not encrypted, and are readable by anyone with get access to the namespace. Enable encryption at rest on etcd and use RBAC to restrict access.
Must the secret be in the same namespace as the Ingress?
Yes. An Ingress can only reference secrets in its own namespace. A wildcard certificate used across namespaces has to be copied into each, or replicated by a controller such as reflector.
How do I update a certificate without downtime?
Apply the new secret with the same name — kubectl create secret tls ... --dry-run=client -o yaml | kubectl apply -f -. Most Ingress controllers watch the secret and reload without dropping connections.

References