Kubernetes Certificate Commands
Complete guide to managing certificates, TLS secrets, and CSRs in Kubernetes clusters
π TLS Secrets Management
Create TLS secret from certificate files
kubectl create secret tls my-tls-secret \
--cert=path/to/cert.pem \
--key=path/to/key.pemCreates a TLS secret for use with ingress or services. The secret contains tls.crt and tls.key.
Create TLS secret in specific namespace
kubectl create secret tls my-tls-secret \
--cert=cert.pem \
--key=key.pem \
--namespace=productionCreate TLS secret from Let's Encrypt certificates
kubectl create secret tls letsencrypt-tls \
--cert=/etc/letsencrypt/live/example.com/fullchain.pem \
--key=/etc/letsencrypt/live/example.com/privkey.pemUse fullchain.pem (not cert.pem) to include intermediate certificates.
View TLS secret contents
kubectl get secret my-tls-secret -o yamlExtract certificate from secret
kubectl get secret my-tls-secret -o jsonpath='{.data.tls\.crt}' | base64 -d > cert.pemExtract private key from secret
kubectl get secret my-tls-secret -o jsonpath='{.data.tls\.key}' | base64 -d > key.pemβ οΈ Warning: Be careful with private keys. Only extract when necessary.
Check certificate expiry date in secret
kubectl get secret my-tls-secret -o jsonpath='{.data.tls\.crt}' | \
base64 -d | \
openssl x509 -noout -enddateUpdate existing TLS secret
# Delete old secret
kubectl delete secret my-tls-secret
# Create new secret with updated certificate
kubectl create secret tls my-tls-secret \
--cert=new-cert.pem \
--key=new-key.pemNote: Pods using the secret need to be restarted to pick up the new certificate.
List all TLS secrets
kubectl get secrets --field-selector type=kubernetes.io/tlsπ Ingress TLS Configuration
Basic ingress with TLS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
tls:
- hosts:
- example.com
secretName: my-tls-secret
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80Ingress with multiple TLS hosts
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-host-ingress
spec:
tls:
- hosts:
- example.com
- www.example.com
secretName: example-tls
- hosts:
- api.example.com
secretName: api-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80Create ingress with TLS via kubectl
kubectl apply -f ingress-tls.yamlCheck ingress TLS configuration
kubectl get ingress my-ingress -o yaml
# Or describe for more details
kubectl describe ingress my-ingressForce SSL redirect (nginx ingress)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
tls:
- hosts:
- example.com
secretName: my-tls-secretTest ingress TLS endpoint
# Get ingress IP
kubectl get ingress my-ingress
# Test with curl
curl -v https://example.com
# Test with openssl
openssl s_client -connect example.com:443 -servername example.comπ Certificate Signing Requests (CSR)
Create Certificate Signing Request
# First, generate a private key and CSR locally
openssl req -new -newkey rsa:4096 -nodes \
-keyout user.key -out user.csr \
-subj "/[email protected]/O=engineering"
# Create Kubernetes CSR object
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: user-csr
spec:
request: $(cat user.csr | base64 | tr -d '\n')
signerName: kubernetes.io/kube-apiserver-client
usages:
- client auth
EOFList pending CSRs
kubectl get csrApprove Certificate Signing Request
kubectl certificate approve user-csrDeny Certificate Signing Request
kubectl certificate deny user-csrView CSR details
kubectl describe csr user-csr
# View the actual CSR content
kubectl get csr user-csr -o jsonpath='{.spec.request}' | base64 -d | openssl req -text -nooutRetrieve signed certificate
kubectl get csr user-csr -o jsonpath='{.status.certificate}' | base64 -d > user.crtDelete CSR
kubectl delete csr user-csrComplete workflow for user authentication certificate
# 1. Generate key and CSR
openssl genrsa -out user.key 4096
openssl req -new -key user.key -out user.csr -subj "/CN=john/O=developers"
# 2. Create and submit CSR to Kubernetes
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: john-csr
spec:
request: $(cat user.csr | base64 | tr -d '\n')
signerName: kubernetes.io/kube-apiserver-client
usages:
- client auth
EOF
# 3. Approve CSR (as cluster admin)
kubectl certificate approve john-csr
# 4. Retrieve signed certificate
kubectl get csr john-csr -o jsonpath='{.status.certificate}' | base64 -d > user.crt
# 5. Create kubeconfig for user
kubectl config set-credentials john \
--client-certificate=user.crt \
--client-key=user.key \
--embed-certs=trueπ Certificate Rotation
Check cluster certificate expiration
# On control plane node
kubeadm certs check-expiration
# Or manually check API server certificate
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -enddateRenew all cluster certificates (kubeadm)
# Renew all certificates
sudo kubeadm certs renew all
# Restart control plane components
sudo systemctl restart kubeletβ οΈ Warning: This should be done on control plane nodes. Ensure you have backups.
Renew specific certificate
sudo kubeadm certs renew apiserver
sudo kubeadm certs renew apiserver-kubelet-client
sudo kubeadm certs renew front-proxy-clientUpdate kubeconfig after certificate renewal
sudo kubeadm init phase kubeconfig admin
# Copy new kubeconfig
sudo cp /etc/kubernetes/admin.conf ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/configRotate application TLS secret
# 1. Get new certificate (e.g., from Let's Encrypt)
# 2. Update secret
kubectl create secret tls my-tls-secret \
--cert=new-cert.pem \
--key=new-key.pem \
--dry-run=client -o yaml | kubectl apply -f -
# 3. Restart pods to pick up new certificate
kubectl rollout restart deployment my-appAutomate certificate rotation with cert-manager
# Install cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
# Create Let's Encrypt issuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginxπ€ cert-manager (Automatic Certificate Management)
Install cert-manager
# Install with kubectl
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
# Or with Helm
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.13.0 \
--set installCRDs=trueCreate Let's Encrypt issuer (HTTP-01)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginxCreate Let's Encrypt issuer (DNS-01 for wildcards)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-dns
solvers:
- dns01:
cloudflare:
email: [email protected]
apiKeySecretRef:
name: cloudflare-api-key
key: api-keyCreate Certificate resource
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com-tls
namespace: default
spec:
secretName: example-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- example.com
- www.example.comIngress with automatic certificate (annotation)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- example.com
secretName: example-com-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80cert-manager will automatically create and manage the certificate based on the annotation.
Check certificate status
kubectl get certificate
kubectl describe certificate example-com-tls
# Check certificate request
kubectl get certificaterequest
kubectl describe certificaterequest example-com-tls-xxxxxManually trigger certificate renewal
# Delete certificate request to trigger renewal
kubectl delete certificaterequest -l certificate.cert-manager.io/certificate-name=example-com-tls
# Or use cmctl (cert-manager CLI)
cmctl renew example-com-tlsCheck cert-manager logs
kubectl logs -n cert-manager deployment/cert-manager -fπ€ Kubeconfig Client Certificates
View current kubeconfig certificate
# View current context
kubectl config current-context
# View certificate details
kubectl config view --raw -o jsonpath='{.users[0].user.client-certificate-data}' | \
base64 -d | \
openssl x509 -text -nooutCheck kubeconfig certificate expiry
kubectl config view --raw -o jsonpath='{.users[0].user.client-certificate-data}' | \
base64 -d | \
openssl x509 -noout -enddateCreate user with certificate authentication
# 1. Generate user private key
openssl genrsa -out user.key 4096
# 2. Create certificate signing request
openssl req -new -key user.key -out user.csr \
-subj "/CN=jane/O=developers"
# 3. Submit CSR to Kubernetes (see CSR section above)
# 4. Get signed certificate from Kubernetes
# 5. Configure kubeconfig
kubectl config set-credentials jane \
--client-certificate=user.crt \
--client-key=user.key \
--embed-certs=true
kubectl config set-context jane-context \
--cluster=my-cluster \
--user=jane \
--namespace=default
kubectl config use-context jane-contextExtract certificate from kubeconfig
# Extract client certificate
kubectl config view --raw -o jsonpath='{.users[?(@.name=="jane")].user.client-certificate-data}' | \
base64 -d > jane.crt
# Extract client key
kubectl config view --raw -o jsonpath='{.users[?(@.name=="jane")].user.client-key-data}' | \
base64 -d > jane.keyRenew admin kubeconfig certificate (kubeadm)
# Renew admin.conf certificate
sudo kubeadm certs renew admin.conf
# Update kubeconfig
sudo cp /etc/kubernetes/admin.conf ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
# Verify
kubectl get nodesConfigure RBAC for certificate-based user
# Create role binding for user (CN from certificate)
kubectl create rolebinding jane-admin \
--clusterrole=admin \
--user=jane \
--namespace=development
# Or create cluster-wide role binding
kubectl create clusterrolebinding jane-cluster-admin \
--clusterrole=cluster-admin \
--user=janeπΈοΈ Service Mesh TLS (mTLS)
Overview of service mesh mTLS
Service meshes like Istio and Linkerd provide automatic mutual TLS (mTLS) between services within the mesh, encrypting service-to-service communication and providing identity verification.
- Automatic certificate issuance and rotation
- Mutual authentication between services
- Zero-trust security model
- Transparent to applications (sidecar proxy handles TLS)
Istio: Check mTLS status
# Check if mTLS is enabled
kubectl get peerauthentication --all-namespaces
# Check destination rules
kubectl get destinationrules --all-namespaces
# Verify mTLS for a specific service
istioctl authn tls-check <pod-name>.<namespace> <service-name>.<namespace>.svc.cluster.localIstio: Enable strict mTLS cluster-wide
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICTIstio: View certificate chain in Envoy proxy
# Get certificate info from Envoy sidecar
kubectl exec <pod-name> -c istio-proxy -- \
curl -s localhost:15000/certs | \
openssl x509 -text -noout
# Check certificate validity
istioctl proxy-config secret <pod-name> -o jsonLinkerd: Check mTLS status
# Check Linkerd mTLS for all pods
linkerd stat deploy -n <namespace>
# View TLS identity for a pod
linkerd identity <pod-name> -n <namespace>
# Check certificate validity
linkerd identity --kubeconfig ~/.kube/configLinkerd: View trust anchor certificate
# Get trust anchor certificate
kubectl get configmap linkerd-identity-trust-roots -n linkerd -o yaml
# View certificate expiry
kubectl get configmap linkerd-identity-trust-roots -n linkerd \
-o jsonpath='{.data.ca-bundle.crt}' | \
openssl x509 -noout -enddateRotate service mesh CA certificate (Istio)
# Generate new CA certificates
mkdir new-ca
cd new-ca
make -f ../tools/certs/Makefile.selfsigned.mk root-ca
# Update Istio CA secret
kubectl create secret generic cacerts -n istio-system \
--from-file=ca-cert.pem \
--from-file=ca-key.pem \
--from-file=root-cert.pem \
--from-file=cert-chain.pem \
--dry-run=client -o yaml | kubectl apply -f -
# Restart Istio control plane
kubectl rollout restart deployment istiod -n istio-systemπ Viewing & Inspecting Certificates
View certificate details from secret
kubectl get secret my-tls-secret -o jsonpath='{.data.tls\.crt}' | \
base64 -d | \
openssl x509 -text -nooutCheck certificate expiry dates across all secrets
for secret in $(kubectl get secrets --field-selector type=kubernetes.io/tls -o name); do
echo "=== $secret ==="
kubectl get $secret -o jsonpath='{.data.tls\.crt}' | \
base64 -d | \
openssl x509 -noout -subject -enddate
echo ""
doneView certificate Subject Alternative Names (SAN)
kubectl get secret my-tls-secret -o jsonpath='{.data.tls\.crt}' | \
base64 -d | \
openssl x509 -noout -text | \
grep -A 1 "Subject Alternative Name"Test ingress TLS connection
# Get ingress external IP
kubectl get ingress my-ingress
# Test with curl
curl -v https://example.com
# Check certificate chain
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -textCompare certificate in secret vs. deployed
# Get fingerprint from secret
kubectl get secret my-tls-secret -o jsonpath='{.data.tls\.crt}' | \
base64 -d | \
openssl x509 -noout -fingerprint
# Get fingerprint from live site
echo | openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -noout -fingerprintπ§ Troubleshooting
Ingress shows "default certificate" instead of custom TLS
Problem: Ingress controller serving default/fallback certificate
Solutions:
- Verify secret exists:
kubectl get secret my-tls-secret - Verify secret is in same namespace as ingress
- Check ingress TLS configuration matches secret name
- Verify certificate covers the ingress hostname (check SAN)
- Restart ingress controller:
kubectl rollout restart deployment -n ingress-nginx nginx-ingress-controller
cert-manager certificate stuck in "pending" state
Problem: Certificate not being issued
Solutions:
- Check certificate status:
kubectl describe certificate - Check challenge status:
kubectl get challenge - Verify HTTP-01 challenge endpoint is accessible (port 80)
- For DNS-01, check DNS provider credentials
- Check cert-manager logs:
kubectl logs -n cert-manager deployment/cert-manager
"x509: certificate signed by unknown authority"
Problem: Certificate chain incomplete or self-signed
Solutions:
- Use fullchain.pem (not cert.pem) for Let's Encrypt
- Verify certificate chain:
openssl verify -CAfile ca-bundle.pem cert.pem - Check if using staging vs. production Let's Encrypt
- For self-signed certs, add CA to trust store
Pods not picking up updated certificate
Problem: Updated secret but pods still use old certificate
Solutions:
- Secrets mounted as volumes aren't automatically reloaded
- Restart pods:
kubectl rollout restart deployment my-app - Or configure app to watch for certificate changes and reload
- Consider using cert-manager with automatic rotation
Debug certificate issues in pod
# Exec into pod
kubectl exec -it my-pod -- sh
# Check mounted certificate
ls -la /etc/ssl/certs/
cat /etc/ssl/certs/tls.crt
# View certificate details
openssl x509 -in /etc/ssl/certs/tls.crt -text -noout
# Test connection from pod
curl -v https://example.comβ Best Practices
Secret management
- Use namespaces to isolate secrets (secrets don't cross namespaces)
- Enable encryption at rest for secrets in etcd
- Use RBAC to restrict access to TLS secrets
- Never commit secrets to version control (use sealed-secrets or external secret operators)
- Regularly rotate certificates (every 30-90 days)
Automation
- Use cert-manager for automatic certificate management
- Set up monitoring for certificate expiry (30, 15, 7 days warnings)
- Automate certificate rotation with proper testing
- Use Let's Encrypt staging for testing before production
- Document certificate renewal procedures
Security
- Use strong key sizes (RSA 4096 or ECDSA P-256 minimum)
- Enable TLS 1.2+ only (disable TLS 1.0 and 1.1)
- Use secure cipher suites
- Implement certificate pinning for critical services
- Regularly audit certificate usage across cluster
Production deployment
- Always use cert-manager in production (not manual secret updates)
- Use DNS-01 challenge for wildcard certificates
- Set up proper monitoring and alerting
- Have rollback plan for certificate issues
- Test certificate rotation in staging first
- Document all certificate configurations in GitOps repo