A Secret holds a small amount of sensitive data (tokens, keys, passwords) so you do not embed it in Pod specs or container images. Secrets can be managed and audited separately from workload manifests.
ConfigMaps hold non-secret configuration; Secrets are intended for confidential data. Kubernetes defines several built-in Secret types. The kubernetes.io/tls type stores a TLS certificate and private key, typically for Ingress TLS termination or for mounting into an application that speaks TLS directly.
This guide shows how to:
- Generate a self-signed certificate for labs (production should use a public CA, internal PKI, or automation such as cert-manager).
- Create a TLS Secret with
kubectl, declarative YAML (stringDataordata), and Terraform. - Reference the Secret from an Ingress and avoid common base64 mistakes.
The Kubernetes API does not fully validate that tls.crt and tls.key match or form a valid chain; bad material usually shows up when the Ingress controller or your app loads the cert.
Prerequisites
- A running cluster and
kubectlconfigured (kubectl cluster-info). opensslfor the examples that generate local test keys.
Generate test certificate and key (OpenSSL)
For learning, you can create a self-signed certificate. In production, keep private keys off shared laptops, use short-lived certs where possible, and rotate on compromise.
Interactive (prompts for DN)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout example.key -out example.crt
Non-interactive with subject (Common Name)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout example.key -out example.crt \
-subj "/CN=app.example.com/O=Demo"
Modern clients and Ingress controllers often require Subject Alternative Names (SANs). A minimal self-signed cert with SAN:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout example.key -out example.crt \
-subj "/CN=app.example.com" \
-addext "subjectAltName=DNS:app.example.com"
If your OpenSSL build does not support -addext, use a small config file with [ v3_req ] and subjectAltName instead.
You will pass example.crt (certificate) and example.key (private key) into the Secret. Never commit real keys to public Git repositories.
Create a TLS Secret with kubectl
The TLS pair must exist on disk first.
kubectl create secret tls example-tls \
--cert=example.crt \
--key=example.key \
--namespace=default
Verify:
kubectl get secret example-tls -o wide
Expected TYPE: kubernetes.io/tls, DATA: 2 (tls.crt and tls.key).
Inspect (values in data are base64-encoded on output):
kubectl get secret example-tls -o yaml
Remove:
kubectl delete secret example-tls
Create the same Secret from YAML
You can use kubectl apply -f or kubectl create -f.
Prefer stringData (PEM pasted as plain text)
Kubernetes encodes stringData to data for you. Include the full PEM, including the BEGIN / END lines—those lines are part of the standard PEM format.
apiVersion: v1
kind: Secret
metadata:
name: example-tls
namespace: default
type: kubernetes.io/tls
stringData:
tls.crt: |
-----BEGIN CERTIFICATE-----
... paste certificate PEM here ...
-----END CERTIFICATE-----
tls.key: |
-----BEGIN PRIVATE KEY-----
... paste private key PEM here ...
-----END PRIVATE KEY-----
Apply:
kubectl apply -f tls-secret.yaml
Git / security: If you version this file, use a private repo, SOPS, Sealed Secrets, External Secrets, or sync from a vault—never store production keys in cleartext in shared revision control.
Using data (base64-encoded PEM)
The data map must contain base64-encoded bytes of the entire PEM file (again including BEGIN/END lines). One-line encoding in the shell:
kubectl create secret tls example-tls \
--cert=example.crt --key=example.key --dry-run=client -o yaml
That prints a valid manifest with data populated; you can save and edit it. To encode manually:
base64 -w0 < example.crt # GNU coreutils; on macOS: base64 -i example.crt | tr -d '\n'
base64 -w0 < example.key
Paste each output as a single line under tls.crt and tls.key in data:.
Correction vs. older tutorials: Kubernetes TLS Secrets store PEM material (as base64 in data). You do not strip the -----BEGIN CERTIFICATE----- / -----END CERTIFICATE----- lines, and you are not switching to “raw DER only” unless your material is actually DER.
Use the Secret on an Ingress
A typical Ingress references the TLS secret by name:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
namespace: default
spec:
tls:
- hosts:
- app.example.com
secretName: example-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: your-service
port:
number: 80
Replace your-service, hostnames, and paths to match your app. Controllers such as ingress-nginx or others use secretName to load the cert for HTTPS.
For production Let’s Encrypt-style automation on Kubernetes, cert-manager usually creates and renews these Secrets for you—see Kubernetes Nginx Ingress in AWS with Certbot / Let’s Encrypt for related patterns.
Create the TLS Secret with Terraform
The data attribute of kubernetes_secret expects base64-encoded strings (same as the API). Pass PEM file contents through base64encode(file(...)) or use filebase64:
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.23"
}
}
}
provider "kubernetes" {
config_path = "~/.kube/config"
}
resource "kubernetes_secret" "example_tls" {
metadata {
name = "example-tls"
namespace = "default"
}
type = "kubernetes.io/tls"
data = {
"tls.crt" = filebase64("${path.module}/example.crt")
"tls.key" = filebase64("${path.module}/example.key")
}
}
Notes:
- Use
type = "kubernetes.io/tls"(not a shortened alias) for clarity. - Do not put raw
file()PEM strings intodatawithout encoding; the API will reject or mis-handle them. - Point
provider "kubernetes"at the right kubeconfig or in-cluster credentials for your environment.
Operations and troubleshooting
| Topic | Guidance |
|---|---|
| Wrong namespace | TLS Secrets are namespaced. Create the Secret in the same namespace as the Ingress (or the workload that mounts it). |
| Certificate mismatch | tls.crt must match the private key in tls.key; hostname/SAN should match URLs clients use. |
kubectl create secret tls errors | Check file paths; ensure the key is RSA/EC PEM the controller accepts. |
| Rotation | Update cert/key and replace the Secret (same name) or create a new Secret and switch secretName, then roll Ingress/controller if needed. |
| Immutability | You can mark Secrets immutable for drift prevention (separate from TLS type); rotation then requires a name or version bump strategy. |
Decode what is stored (local shell only, handle with care):
kubectl get secret example-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -subject -dates
Related content
- Getting started with Kubernetes – Kubernetes components
- Kubernetes Nginx Ingress in AWS with Certbot / Let’s Encrypt
- Working with Kubernetes Jobs and CronJobs
- Ingress TLS documentation
Conclusion
kubernetes.io/tls Secrets bundle tls.crt and tls.key for Ingress and workloads. The fastest paths are kubectl create secret tls or YAML with stringData and full PEM blocks. In Terraform, filebase64 (or base64encode(file(...))) matches what the Kubernetes API expects. For production clusters, pair this knowledge with cert-manager or your PKI so issuance and renewal stay automated.