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.
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 -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 #
kubectl get secret example-com-tls -n production \
-o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -subject -dates -ext subjectAltName 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
- Kubernetes secrets — the kubernetes.io/tls type and its required keys