Fix 'tls: certificate signed by unknown authority' β€” kubectl Can't Connect to API Server

intermediate☸️ Kubernetes2026-04-23| Kubernetes 1.20+, kubectl (any version), Linux/macOS/Windows, kubeadm clusters, EKS, GKE, on-prem

Error Message

Unable to connect to the server: tls: certificate signed by unknown authority
#tls#certificate#kubectl#kubeconfig#api-server#kubernetes

The cluster was fine yesterday

You run a routine kubectl get pods and get slapped with this:

Unable to connect to the server: tls: certificate signed by unknown authority

Nothing changed β€” or so you thought. kubectl is refusing to trust the certificate the API server presented. The TLS handshake failed before a single byte of cluster data came back.

Four things typically cause this: the cluster was rebuilt, certificates were rotated, you copied a kubeconfig from another machine, or the API server cert quietly expired overnight.

Debug: figure out which cert is the problem

Step 1 β€” Check what kubeconfig you're actually using

kubectl config view --raw | grep server
echo $KUBECONFIG

A set KUBECONFIG env var overrides the default ~/.kube/config. Confirm you're looking at the right file before going further.

Step 2 β€” Test the TLS connection directly

# Get the API server address from kubeconfig
kubectl config view --raw -o jsonpath='{.clusters[0].cluster.server}'

# Then probe it with openssl
openssl s_client -connect <api-server-host>:6443 2>&1 | head -30

Scan the certificate chain in the output. It shows whether the cert is self-signed, what the CN/SAN fields say, and the exact expiry date.

Step 3 β€” Check certificate expiry

# On a control plane node
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates

# Or check all certs at once (kubeadm clusters)
kubeadm certs check-expiration

See a Not After date in the past? That's your culprit.

Step 4 β€” Inspect the CA cert embedded in kubeconfig

# Extract and decode the CA cert from kubeconfig
kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' \
  | base64 -d | openssl x509 -noout -text | grep -A2 'Validity\|Issuer\|Subject'

Compare the issuer here against what the API server is actually presenting. A mismatch means the kubeconfig is stale β€” the cluster moved on without it.

Solutions β€” pick the one that matches your situation

Scenario A: Kubeconfig is stale (most common)

The cluster's CA or API server cert was rotated, but your local kubeconfig still carries the old CA data.

# If you have SSH access to the control plane node
scp root@<control-plane-ip>:/etc/kubernetes/admin.conf ~/.kube/config

# Or on the control plane itself
cat /etc/kubernetes/admin.conf

Grab the fresh admin.conf and replace your local kubeconfig. Fastest fix on this list.

Scenario B: API server certificate expired (kubeadm)

# Renew all certificates
sudo kubeadm certs renew all

# Verify renewal
kubeadm certs check-expiration

# Restart control plane components to pick up new certs
sudo systemctl restart kubelet

# Refresh your kubeconfig
sudo cp /etc/kubernetes/admin.conf ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config

Static pods for the API server restart automatically β€” usually within 60 seconds. If they don't move, force it:

sudo crictl ps | grep kube-apiserver
# Note the container ID, then:
sudo crictl stop <container-id>

Scenario C: Self-signed cert not trusted (new cluster setup)

First-time connection to a cluster, and the CA cert isn't in your kubeconfig. Or someone handed you a raw endpoint URL with no CA bundle attached.

# Option 1: Add the CA cert to your kubeconfig
kubectl config set-cluster <cluster-name> \
  --certificate-authority=/path/to/ca.crt \
  --embed-certs=true

# Option 2: Use insecure-skip-tls-verify (TEMPORARY DEBUG ONLY)
kubectl --insecure-skip-tls-verify get nodes

Do not leave insecure-skip-tls-verify: true in a kubeconfig permanently. Use the flag only to confirm the server is reachable while you sort out the cert β€” then remove it.

Scenario D: API server SAN mismatch

The IP or hostname you're connecting through isn't listed in the certificate's Subject Alternative Names. This breaks after adding a load balancer in front of the API server, or after the control plane node gets a new IP.

# Check what SANs the current cert covers
openssl s_client -connect <api-server-ip>:6443 2>/dev/null \
  | openssl x509 -noout -text | grep -A1 'Subject Alternative'

Missing IP or hostname? Reissue the cert with the correct SANs. For kubeadm:

# Delete the existing apiserver cert
sudo rm /etc/kubernetes/pki/apiserver.{crt,key}

# Add the new IP/hostname and regenerate
sudo kubeadm init phase certs apiserver \
  --apiserver-cert-extra-sans=<new-ip>,<new-hostname>

# Restart kubelet
sudo systemctl restart kubelet

Scenario E: Managed cluster (EKS/GKE/AKS) β€” stale kubeconfig

# EKS β€” refresh kubeconfig
aws eks update-kubeconfig --name <cluster-name> --region <region>

# GKE
gcloud container clusters get-credentials <cluster-name> --zone <zone>

# AKS
az aks get-credentials --resource-group <rg> --name <cluster-name>

Managed clusters rotate CA data on their own schedule. Always pull a fresh kubeconfig from the cloud CLI. Copying kubeconfigs between machines is how you end up back here.

Verify the fix

# Basic connectivity
kubectl cluster-info

# Should return node list without TLS errors
kubectl get nodes

# Double-check cert validity
kubeadm certs check-expiration  # if kubeadm cluster

When kubectl cluster-info shows the API server URL and the CoreDNS line with no TLS warnings, you're done.

Set a reminder before it happens again

Default kubeadm certificate validity is 1 year β€” 365 days, to the hour. Without automation, this exact error comes back next year at the worst possible time.

# Add to crontab on the control plane node β€” runs cert check monthly
0 9 1 * * kubeadm certs check-expiration 2>&1 | mail -s "K8s cert check" ops@yourcompany.com

# Or set up auto-renewal 30 days before expiry
0 9 1 * * bash -c 'kubeadm certs check-expiration 2>&1 | grep -q "<30d" && kubeadm certs renew all'

Plenty of teams just run kubeadm certs renew all in a quarterly maintenance window and call it done. That works too. Both beat getting paged at 2 AM because a cert aged out.

Related Error Notes