How to create a Kubernetes TLS/SSL secret

Create kubernetes.io/tls Secrets with kubectl, YAML (stringData), or Terraform: generate test certs with OpenSSL, attach certificates to Ingress, and avoid common base64 and PEM mistakes.

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:

  1. Generate a self-signed certificate for labs (production should use a public CA, internal PKI, or automation such as cert-manager).
  2. Create a TLS Secret with kubectl, declarative YAML (stringData or data), and Terraform.
  3. 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 kubectl configured (kubectl cluster-info).
  • openssl for 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)

1
2
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout example.key -out example.crt

Non-interactive with subject (Common Name)

1
2
3
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:

1
2
3
4
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.

1
2
3
4
kubectl create secret tls example-tls \
  --cert=example.crt \
  --key=example.key \
  --namespace=default

Verify:

1
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):

1
kubectl get secret example-tls -o yaml

Remove:

1
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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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:

1
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:

1
2
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:

1
2
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 into data without 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

TopicGuidance
Wrong namespaceTLS Secrets are namespaced. Create the Secret in the same namespace as the Ingress (or the workload that mounts it).
Certificate mismatchtls.crt must match the private key in tls.key; hostname/SAN should match URLs clients use.
kubectl create secret tls errorsCheck file paths; ensure the key is RSA/EC PEM the controller accepts.
RotationUpdate cert/key and replace the Secret (same name) or create a new Secret and switch secretName, then roll Ingress/controller if needed.
ImmutabilityYou 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):

1
kubectl get secret example-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -subject -dates

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.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy