Fix kubectl 'x509: certificate signed by unknown authority'

Quick answer
This error means kubectl can't verify the API server's TLS certificate against the CA in your kubeconfig. Here's how to find which of the five common causes is yours and fix it.
- What this error means
- Step 1: See the actual error
- Cause 1: Stale kubeconfig after the cluster was recreated or rotated
- Cause 2: A corporate proxy or TLS-inspecting firewall is re-signing traffic
- Cause 3: A self-signed or private cluster cert that isn't trusted
9 min read · Kubernetes
Fix kubectl 'x509: certificate signed by unknown authority'
You run any kubectl command and it dies before it does anything:
Unable to connect to the server: x509: certificate signed by unknown authority
Or the sibling error:
Unable to connect to the server: x509: certificate is valid for 10.0.0.1, not 34.122.15.9
Both are TLS trust failures. This walks through the five causes I actually hit in production, most common first, with the exact fix for each.
What this error means
Every Kubernetes API server presents a TLS certificate. Your kubeconfig stores the cluster's certificate authority (CA) in the certificate-authority-data field. When you run kubectl, it opens an HTTPS connection to the API server and checks that the server's certificate was signed by that CA — and that the address you dialed matches one of the certificate's Subject Alternative Names (SANs).
certificate signed by unknown authority→ the server's cert was signed by a CA that is not the one in your kubeconfig. The trust chain doesn't add up.certificate is valid for X, not Y→ the trust chain is fine, but you're reaching the server by an address (Y) that isn't listed in the cert's SANs.
kubectl refuses to talk to a server it can't verify. That is the correct behavior — it's protecting you from a man-in-the-middle. So the fix is almost always to make your kubeconfig match reality, not to disable the check.
Step 1: See the actual error
Run any command with more detail so you can tell the two variants apart:
kubectl get nodes -v=6Then inspect what your kubeconfig thinks the server and CA are:
1# Which cluster/context are you actually using?
2kubectl config current-context
3
4# The server URL kubectl is dialing
5kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'
6
7# Is CA data embedded, or pointing at a file?
8kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d | openssl x509 -noout -issuer -subject -datesCompare that against the certificate the server is actually serving:
# Replace host:port with your API server from the server URL above
openssl s_client -connect 34.122.15.9:443 -showcerts </dev/null 2>/dev/null | openssl x509 -noout -issuer -subject -ext subjectAltName -datesIf the issuer on the server cert doesn't match the CA subject in your kubeconfig, you have a trust problem (Causes 1–3). If they match but the SANs don't include your server address, it's Cause 4. If everything matches but dates look off, it's Cause 5.
Cause 1: Stale kubeconfig after the cluster was recreated or rotated
This is by far the most common. Someone tore down and recreated the cluster (or the CA / control-plane cert was rotated) and your local kubeconfig still holds the old CA data. The endpoint answers, but with a certificate signed by a brand-new CA your kubeconfig has never seen.
Fix: re-fetch credentials from your provider. This overwrites the CA data for that cluster.
1# EKS
2aws eks update-kubeconfig --name my-cluster --region us-east-1
3
4# GKE
5gcloud container clusters get-credentials my-cluster --region us-central1
6
7# AKS
8az aks get-credentials --resource-group my-rg --name my-cluster --overwrite-existingFor a self-managed cluster, re-copy the admin kubeconfig from the control-plane node:
scp root@control-plane:/etc/kubernetes/admin.conf ~/.kube/configIf you have several stale entries, delete the old cluster/context/user before re-fetching so nothing merges badly:
kubectl config delete-cluster my-cluster
kubectl config delete-context my-context
kubectl config delete-user my-userCause 2: A corporate proxy or TLS-inspecting firewall is re-signing traffic
On corporate networks, a TLS-inspection proxy (Zscaler, Netskope, a Palo Alto firewall, etc.) terminates your HTTPS connection and re-signs it with the company's own CA. kubectl then sees a certificate issued by "Acme Corp Root CA" instead of your cluster CA, and rejects it.
Tell-tale sign: the issuer from the openssl s_client output in Step 1 is your employer's name, not the cluster.
Fix — trust the corporate CA: get the corporate root CA in PEM form from IT and add it to the system trust store so kubectl (via Go's TLS stack) will trust the inspected chain.
# Linux (Debian/Ubuntu)
sudo cp corp-root-ca.crt /usr/local/share/ca-certificates/corp-root-ca.crt
sudo update-ca-certificates
# macOS
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain corp-root-ca.crtFix — or bypass the proxy for the cluster endpoint: if the API server is a private/internal address that shouldn't be inspected, exclude it.
export NO_PROXY="$NO_PROXY,34.122.15.9,.internal.mycompany.com"Stuck on this in production?
We debug exactly this kind of issue for platform teams — usually in a single working session.
Cause 3: A self-signed or private cluster cert that isn't trusted
Bare-metal, kubeadm, k3s, and homelab clusters usually have their own self-signed CA. If you copied only the server: URL into a kubeconfig without the matching CA data, kubectl has nothing to verify against.
Fix: embed the correct CA. Grab it from the control-plane node and reference it:
# Point at the CA file
kubectl config set-cluster my-cluster \
--server=https://34.122.15.9:6443 \
--certificate-authority=/etc/kubernetes/pki/ca.crt \
--embed-certs=true--embed-certs=true inlines the CA into the kubeconfig as certificate-authority-data so the file is portable.
Dev-only escape hatch: you can skip verification entirely. Do this only on a throwaway local cluster — it disables the man-in-the-middle protection completely and any network attacker can impersonate your API server, so never use it against a real or shared cluster:
kubectl config set-cluster my-cluster --insecure-skip-tls-verify=trueCause 4: 'valid for X, not Y' — you're hitting an address not in the cert SANs
The trust chain is fine, but the certificate was issued for a specific set of names/IPs (its SANs) and you're connecting by a different one — commonly the raw node IP instead of the load-balancer hostname, or an extra DNS name someone put in front of the API.
Check the SANs on the served cert:
echo | openssl s_client -connect my-api.example.com:6443 2>/dev/null | openssl x509 -noout -ext subjectAltNameFix — use an address that's actually in the SAN list. Update the server: URL to a name the cert covers:
kubectl config set-cluster my-cluster --server=https://my-api.example.com:6443Fix — or add the SAN to the cert. For kubeadm, add the extra name and regenerate the API server certificate:
# kubeadm ClusterConfiguration
apiServer:
certSANs:
- "my-api.example.com"
- "34.122.15.9"# On the control-plane node: back up, remove old certs, regenerate
sudo rm /etc/kubernetes/pki/apiserver.crt /etc/kubernetes/pki/apiserver.key
sudo kubeadm init phase certs apiserver --config kubeadm-config.yaml
# then restart the kube-apiserver (kill the static pod)Cause 5: Large clock skew breaking certificate validity
TLS validation checks the certificate's notBefore / notAfter window against your machine's clock. If your laptop or a node has drifted by hours (a suspended VM, a dead RTC battery, a container without NTP), a perfectly good certificate looks "not yet valid" or "expired," and Go surfaces it as an x509 verification failure.
Check the skew: compare the cert dates from Step 1 against your local clock.
date -uFix: sync time via NTP.
1# Linux with systemd
2sudo timedatectl set-ntp true
3timedatectl status
4
5# Force an immediate sync
6sudo chronyc makestep # chrony
7sudo ntpdate -u pool.ntp.org # older ntp setupsOn macOS, enable "Set date and time automatically" in System Settings, or run sudo sntp -sS time.apple.com.
Quick reference
1# 1. What server + CA does my kubeconfig use?
2kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'
3
4# 2. What cert is the server actually serving (issuer, SANs, dates)?
5echo | openssl s_client -connect <host:port> 2>/dev/null \
6 | openssl x509 -noout -issuer -ext subjectAltName -dates
7
8# 3. Most common fix: re-fetch credentials
9aws eks update-kubeconfig --name <cluster> --region <region>| Symptom | Likely cause | Fix |
|---|---|---|
| Cluster was just recreated/rotated | Stale kubeconfig CA | Re-fetch credentials (Cause 1) |
| Issuer is your employer's name | TLS-inspection proxy | Trust corp CA or NO_PROXY (Cause 2) |
| Homelab / kubeadm / k3s | Self-signed CA not embedded | Embed certificate-authority-data (Cause 3) |
valid for X, not Y | Address not in SANs | Use a covered name or add certSAN (Cause 4) |
| Clock is hours off | Clock skew | Sync NTP (Cause 5) |
Frequently Asked Questions
Why does kubectl reject a certificate my browser accepts?
Because they trust different things. Your browser trusts the operating system's public CA store (and often your corporate CA). kubectl verifies against the CA embedded in your kubeconfig's certificate-authority-data, which is scoped to that one cluster. A cert your browser is fine with can still be "unknown authority" to kubectl.
Is insecure-skip-tls-verify: true ever safe to use?
Only on a disposable local cluster you fully control. It turns off server verification entirely, so anyone able to intercept your traffic can impersonate the API server and capture your credentials. Never enable it against a shared, staging, or production cluster — fix the CA data instead.
How do I know if a corporate proxy is the cause?
Run openssl s_client -connect <api-host>:443 and read the issuer line. If it names your company or a security vendor (Zscaler, Netskope, Palo Alto) rather than your cluster or cloud provider, a TLS-inspection proxy is re-signing the connection. Trust that CA or exclude the endpoint via NO_PROXY.
What's the difference between 'unknown authority' and 'valid for X, not Y'?
unknown authority is a trust-chain failure — the signing CA isn't in your kubeconfig. valid for X, not Y is a name-mismatch — the CA is trusted, but you connected by an address that isn't in the certificate's SAN list. The first is fixed by refreshing the CA; the second by using the right server URL or adding a SAN.
Will re-running aws eks update-kubeconfig break my other contexts?
No. It only writes the cluster, context, and user entries for that one cluster, merging them into your existing ~/.kube/config. Your other contexts are untouched. If an old entry with the same name is stale, delete it first with kubectl config delete-cluster so nothing conflicts.
See also
- Fix: kubectl connection refused to localhost:8080 — the other kubeconfig error you hit when no cluster is configured
- The Kubernetes Debugging Guide — a systematic workflow for diagnosing cluster and pod issues
- Terraform + EKS: Infrastructure as Code — provision clusters so kubeconfig and CA data stay reproducible
Fighting recurring cluster access or TLS issues across your team? Talk to us at Coding Protocols — we build reproducible kubeconfig and access workflows so credentials stop breaking silently.
Official References
- Debug Pods — reading pod status, events and container states
- kubectl reference — command syntax, output formats and selectors
Was this article helpful?
Be the first to rate this article
Related Topics
Found this useful? Share it.


