Symptoom: control plane onbereikbaar met x509-fouten
Je doet een routinematige kubectl get nodes en het cluster is weg:
Unable to connect to the server: x509: certificate has expired or is not yet valid:
current time 2026-04-24T10:14:23Z is after 2026-04-23T16:42:11Z
Of, op een control-plane node, draait de kube-apiserver static pod in CrashLoopBackOff en laat crictl logs voor de etcd- of apiserver-container dit zien:
W0424 10:14:23.118233 authentication.go:73] Unable to authenticate the request
due to an error: x509: certificate has expired or is not yet valid
Beheer je je control plane zelf en draait het cluster ongeveer een jaar zonder dat er een kubeadm upgrade overheen is gegaan, dan is dit vrijwel zeker verlopen kubeadm-PKI. De fix is reëel maar mechanisch: vernieuwen, herstarten, verifiëren.
Dit artikel gaat over clusters die met kubeadm zijn opgezet. EKS, GKE en AKS regelen control-plane-certificaatrotatie voor je, dus dit pad geldt daar niet. AKS roteert non-CA-certificaten zelfs automatisch op 80% van hun valid time op clusters die na maart 2022 zijn aangemaakt of geüpgraded. De kubelet-client-certificaten op worker nodes worden door Kubernetes zelf beheerd, los van het kubeadm-pad dat hier centraal staat.
Wat dit eigenlijk betekent
kubeadm zet een zelfondertekende PKI-hiërarchie op onder /etc/kubernetes/pki/. Drie CA's (Kubernetes CA, etcd CA, front-proxy CA), een service-account-keypair, en een waaier aan leaf-certificaten die elke component-naar-component-verbinding in de control plane authenticeren.
Standaard gaan leaf-certificaten 1 jaar mee (8760h), CA's 10 jaar (87600h). Als een leaf-certificaat verloopt, kan de bijhorende component zich niet meer authenticeren bij de API server. En dan ligt de hele control plane plat: de kube-apiserver komt niet meer bij etcd, controllers kunnen geen resources lijsten, de scheduler kan geen pods binden, en kubectl op je werkstation accepteert het servercertificaat niet meer.
De CA's zijn nog wel goed. Alleen de leaves zijn verlopen. Daarom werkt vernieuwen ook zonder dat je het cluster opnieuw moet opbouwen: kubeadm gebruikt de CA-keys die nog op disk staan om verse leaves uit te geven met dezelfde SANs.
Twee misverstanden om uit de weg te ruimen voor je begint:
kubeadm certs renew allroteert de CA's niet. Het vernieuwt alleen leaf-certificaten en kubeconfig-client-certs. De CA's behouden hun oorspronkelijke 10 jaar. Loopt een CA zelf richting expiry, dan is dat een ander (en lastiger) traject.- Na vernieuwen blijft de API server het oude certificaat gebruiken tot je hem herstart. Static pods op de control plane pikken nieuwe bestanden op disk niet automatisch op. Je moet ze echt even herstarten.
Veelvoorkomende oorzaken, op volgorde van waarschijnlijkheid
- Het cluster is met kubeadm opgezet en is bijna een jaar niet geüpgraded. Veruit de vaakst voorkomende oorzaak. kubeadm vernieuwt certificaten automatisch tijdens een
kubeadm upgrade applyals ze binnen 180 dagen van expiry zijn, oftewel ongeveer 50% van de leaf-levensduur. Sla genoeg upgradecycli over en je rijdt zo de muur in.
- Een node was offline tijdens het renewal-window. Liep
kubeadm upgradewel, maar lag een control-plane node op dat moment plat, dan zijn de leaves op die node niet vernieuwd. - External CA mode is in gebruik. Vindt kubeadm
ca.crtzonderca.key, dan schakelt het over op external CA mode en weigert het zelf certificaten uit te geven. Je moet dan extern vernieuwen en de bestanden handmatig terugzetten op elke node.kubeadm certs renew allfaalt metexternal CA mode: cannot renew certificates. - Kubelet-client-certificaat los verlopen. De kubelet roteert zijn eigen client-certificaat tussen 30% en 10% van de resterende levensduur. Maar als de API server al onbereikbaar was toen dat rotatievenster opende, kon de kubelet niet bij het CSR-endpoint en is het certificaat alsnog verlopen. Dat geeft specifiek
system:nodex509-fouten en heeft een eigen fix (zie verderop). - Klokafwijking op de node. Springt de systeemklok vooruit, dan lijken certificaten "not yet valid" of "expired" terwijl er niets aan de hand is. Check
timedatectl statusvoor je expiry als oorzaak aanneemt.
Diagnose: welke certificaten zijn verlopen
SSH naar een willekeurige control plane node en draai:
sudo kubeadm certs check-expiration
Verwachte output (kolommen ingekort):
CERTIFICATE EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
admin.conf Apr 23, 2026 16:42 UTC no
apiserver Apr 23, 2026 16:42 UTC no
apiserver-etcd-client Apr 23, 2026 16:42 UTC no
apiserver-kubelet-client Apr 23, 2026 16:42 UTC no
controller-manager.conf Apr 23, 2026 16:42 UTC no
etcd-healthcheck-client Apr 23, 2026 16:42 UTC no
etcd-peer Apr 23, 2026 16:42 UTC no
etcd-server Apr 23, 2026 16:42 UTC no
front-proxy-client Apr 23, 2026 16:42 UTC no
scheduler.conf Apr 23, 2026 16:42 UTC no
super-admin.conf Apr 23, 2026 16:42 UTC no
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
ca Apr 21, 2035 16:42 UTC 8y no
etcd-ca Apr 21, 2035 16:42 UTC 8y no
front-proxy-ca Apr 21, 2035 16:42 UTC 8y no
<invalid> in de kolom RESIDUAL TIME bevestigt dat ze verlopen zijn. De CA's onderaan moeten nog jaren te gaan hebben. super-admin.conf zie je op clusters die met kubeadm 1.29 of later zijn opgezet of bijgewerkt; dat bestand is daar geïntroduceerd als break-glass-credential los van de dagelijkse admin.conf.
Kun je het cluster niet bereiken vanaf je werkstation, controleer dan ook even of de nodeklok klopt:
timedatectl status
Je wilt System clock synchronized: yes. Een node die maandenlang voorop is gelopen toont "verlopen" certificaten die in werkelijkheid prima zijn.
Voor een individueel certificaatbestand (handig als check-expiration zelf om wat voor reden ook faalt):
sudo openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates
Output:
notBefore=Apr 23, 2025 16:42:11 GMT
notAfter=Apr 23, 2026 16:42:11 GMT
Stap 1: backup van de PKI-directory en admin.conf
Vernieuwen overschrijft bestanden ter plekke. Voor je iets onomkeerbaars doet, snapshot eerst de huidige state op elke control-plane node:
sudo cp -a /etc/kubernetes/pki /etc/kubernetes/pki.bak-$(date +%Y%m%d)
sudo cp -a /etc/kubernetes/admin.conf /etc/kubernetes/admin.conf.bak-$(date +%Y%m%d)
sudo cp -a /etc/kubernetes/controller-manager.conf /etc/kubernetes/controller-manager.conf.bak-$(date +%Y%m%d)
sudo cp -a /etc/kubernetes/scheduler.conf /etc/kubernetes/scheduler.conf.bak-$(date +%Y%m%d)
sudo cp -a /etc/kubernetes/kubelet.conf /etc/kubernetes/kubelet.conf.bak-$(date +%Y%m%d)
Houd je etcd-snapshots bij (en dat zou je moeten doen, zie Kubernetes etcd: backup, restore en disaster recovery), maak dan ook nu even een verse. Vernieuwen raakt etcd-data niet aan, maar als er ergens in deze procedure wat misgaat, wil je een recente snapshot achter de hand hebben.
Stap 2: vernieuwen met kubeadm certs renew all
Draai dit op de eerste control-plane node:
sudo kubeadm certs renew all
Verwachte output:
[renew] Reading configuration from the cluster...
[renew] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself renewed
certificate for serving the Kubernetes API renewed
certificate the apiserver uses to access etcd renewed
certificate for the API server to connect to kubelet renewed
certificate embedded in the kubeconfig file for the controller manager to use renewed
certificate for liveness probes to healthcheck etcd renewed
certificate for etcd nodes to communicate with each other renewed
certificate for serving etcd renewed
certificate for the front proxy client renewed
certificate embedded in the kubeconfig file for the scheduler manager to use renewed
certificate embedded in the kubeconfig file for the super-admin renewed
Done renewing certificates. You must restart the kube-apiserver, kube-controller-manager,
kube-scheduler and etcd, so that they can use the new certificates.
Het commando draait onvoorwaardelijk, ongeacht expiry, en hergebruikt de SANs van de bestaande certificaten. De kubeadm-reference documenteert elk individueel sub-commando voor als je ooit één enkel certificaat wil vernieuwen (kubeadm certs renew apiserver, etc.).
Bij HA-clusters herhaal je dit commando op elke control-plane node. Iedere node heeft zijn eigen kopie van de PKI; vernieuwen op één node propageert niet.
Faalt kubeadm certs renew all met external CA mode: cannot renew certificates, dan zit je niet in kubeadm-managed mode. De CA-private-key staat bewust niet op disk. Je moet vervangende leaf-certificaten uitgeven vanuit je externe CA en die handmatig in /etc/kubernetes/pki/ zetten. kubeadm helpt je hier niet verder mee.
Stap 3: control-plane-componenten herstarten
De static pods draaien nog met de oude certificaten in geheugen. Ze pikken de nieuwe bestanden op disk pas op als ze herstarten. De cleane manier om dat af te dwingen: elk manifest uit /etc/kubernetes/manifests/ halen en weer terugzetten. De kubelet polt die directory elke 20 seconden (fileCheckFrequency) en reconcilt op basis van welke manifests hij ziet.
# Verplaats alle vier de control-plane manifests
sudo mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/
sudo mv /etc/kubernetes/manifests/kube-controller-manager.yaml /tmp/
sudo mv /etc/kubernetes/manifests/kube-scheduler.yaml /tmp/
sudo mv /etc/kubernetes/manifests/etcd.yaml /tmp/
# Wacht ~25 seconden, controleer dan dat de containers gestopt zijn
sleep 25
sudo crictl ps | grep -E 'apiserver|controller-manager|scheduler|etcd_'
Je hoort niets terug te krijgen. Staan er nog containers te draaien, wacht dan nog 10 seconden en check opnieuw. De kubelet heeft soms een extra polcyclus nodig.
Daarna terugzetten:
sudo mv /tmp/etcd.yaml /etc/kubernetes/manifests/
sudo mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/
sudo mv /tmp/kube-controller-manager.yaml /etc/kubernetes/manifests/
sudo mv /tmp/kube-scheduler.yaml /etc/kubernetes/manifests/
Etcd eerst weer omhoog. De API server is daarvan afhankelijk. Wacht 30 tot 60 seconden tot de kubelet alle vier de containers opnieuw heeft aangemaakt.
Eén opmerking specifiek over etcd: sinds etcd v3.2.0 worden TLS-certificaten herladen bij elke client-connectie, dus een al draaiende etcd pikt de nieuwe server.crt en peer.crt op zodra er nieuwe verbindingen binnenkomen. In theorie hoef je etcd voor de certificaatwissel dus niet te herstarten. In de praktijk is een schone restart simpeler dan uitvogelen welke bestaande connecties welk certificaat gebruiken, en geeft het je een duidelijk before/after voor de verificatie.
Tot slot: herstart de kubelet zelf zodat hij de vernieuwde kubelet.conf inleest (met een kanttekening; zie de speciale gevallen verderop):
sudo systemctl restart kubelet
Stap 4: verifieer de vernieuwing en herstel clustertoegang
Bevestig op de control-plane node de nieuwe expiratiedata:
sudo kubeadm certs check-expiration
Elk leaf-certificaat hoort nu ongeveer een jaar resterend te tonen.
Controleer dat de static pods terug zijn:
sudo crictl ps | grep -E 'apiserver|controller-manager|scheduler|etcd_'
Je hoort alle vier de containers te zien.
Vanaf je werkstation refereert je lokale kubeconfig nog naar het oude client-certificaat. Zelfs als de API server gezond is, authenticeert kubectl niet. Refresh het dus vanuit de vernieuwde admin.conf:
# Op de control-plane node
sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Of, vanaf je werkstation, kopieer het bestand via SSH terug en overschrijf ~/.kube/config. Dit is de stap waarop mensen het vaakst struikelen: de API server is goed, etcd is goed, kubectl roept x509: certificate has expired, en de oorzaak is gewoon een muffe lokale kubeconfig. Op het Kubernetes-discussionforum staan meerdere threads over precies deze verwarring.
Je weet dat het herstel gelukt is als:
kubectl get nodes
kubectl get pods --all-namespaces
hun normale output geven zonder x509-fouten en alle nodes Ready melden.
Speciaal geval: etcd-certificaten (eigen renewal-pad; external etcd-caveat)
kubeadm certs renew all dekt etcd-certificaten af als etcd als kubeadm-managed static pod draait (de standaard stacked etcd-topologie). De vier etcd-gerelateerde certificaten zijn:
| Certificaat | Wat het doet |
|---|---|
etcd-server |
Server cert dat etcd presenteert aan clients op poort 2379 |
etcd-peer |
Server- en client-cert voor verkeer tussen members op poort 2380 |
etcd-healthcheck-client |
Client-cert voor de liveness probe van etcd |
apiserver-etcd-client |
Client-cert dat de kube-apiserver gebruikt om met etcd te praten |
Draai je etcd extern (op aparte VM's of een eigen cluster, niet stacked op de control-plane nodes), dan kan kubeadm de etcd-certificaten niet voor je vernieuwen. Je beheert etcd's PKI dan via het proces dat het oorspronkelijk heeft opgezet (vaak een aparte etcd-managed CA, of handmatig uitgegeven certificaten). Aan de kubeadm-kant valt alleen apiserver-etcd-client binnen de scope, want die is wel met de etcd-CA ondertekend maar staat in /etc/kubernetes/pki/ voor gebruik door de API server. De rest woont bij etcd.
Bij stacked-etcd-kubeadm-setups (wat kubeadm init standaard oplevert) heb je geen aparte procedure nodig. kubeadm certs renew all dekt alle vier af, en de static-pod-restart in stap 3 pakt ze op.
Voor het bredere operationele plaatje rond etcd, inclusief snapshots en disaster recovery, zie Kubernetes etcd: backup, restore en disaster recovery.
Speciaal geval: kubelet-client-certificaat (auto-rotatie, niet in renew all)
Het kubelet-client-certificaat (/var/lib/kubelet/pki/kubelet-client-current.pem) wordt niet beheerd door kubeadm certs renew all. Het wordt door de kubelet zelf geroteerd, tussen 30% en 10% van de resterende levensduur, via de RotateKubeletClientCertificate feature gate (stable en standaard aan sinds Kubernetes 1.19). De kubelet plaatst een CertificateSigningRequest bij de API server, de controller-manager keurt die automatisch goed, en de kubelet schrijft het nieuwe certificaat naar disk.
Eén edge case waar dit omvalt: als de API server al onbereikbaar was toen het rotatievenster opende, kan de kubelet het CSR-endpoint niet bereiken en verloopt het certificaat alsnog. Je ziet dit op een node die tijdens het rotatievenster offline was, of op elke node als de API server zelf platlag (precies het scenario uit dit artikel).
kubelet.conf is ook apart geregeld. De historische kubeadm-issues #1361 en #2185 leggen uit waarom: het embedded client-certificaat in kubelet.conf zou worden vervangen door het auto-geroteerde certificaat dat elders staat (/var/lib/kubelet/pki/), dus kubeadm raakt kubelet.conf bewust niet aan tijdens certs renew all.
Heb je een verlopen kubelet-client-certificaat en is de auto-rotatie al gefaald, dan is de herstelprocedure:
# Op de getroffen node, regenereer kubelet.conf vanuit de CA
sudo kubeadm kubeconfig user \
--org system:nodes \
--client-name system:node:$(hostname) \
> /tmp/kubelet.conf
# Vervang de bestaande kubelet.conf
sudo mv /etc/kubernetes/kubelet.conf /etc/kubernetes/kubelet.conf.expired
sudo mv /tmp/kubelet.conf /etc/kubernetes/kubelet.conf
# Verwijder de oude auto-geroteerde certs zodat de kubelet vers bootstrapt
sudo rm /var/lib/kubelet/pki/kubelet-client-*.pem
# Herstart de kubelet
sudo systemctl restart kubelet
Na de herstart gebruikt de kubelet kubelet.conf om te bootstrappen, plaatst een CSR, en de controller-manager (die nu zelf weer een vers certificaat heeft en weer kan signen) keurt die automatisch goed. Een nieuwe kubelet-client-current.pem verschijnt binnen een paar seconden in /var/lib/kubelet/pki/. Volg de kubelet-logs: journalctl -u kubelet -f.
Je weet dat het werkt als de node terugkeert naar Ready:
kubectl get node $(hostname)
Wanneer escaleren
Brengt deze procedure het cluster niet terug, verzamel dan dit voor je hulp inschakelt:
- Output van
sudo kubeadm certs check-expirationvan elke getroffen node - Output van
kubectl version(specifiek de server-build, ook als de client het cluster niet kan bereiken) - Output van
timedatectl statusvan elke getroffen node (klokafwijking is een stille moordenaar) - De exacte x509-foutregel uit
crictl logsvoor de kube-apiserver- en etcd-containers - De eerste 50 regels van
journalctl -u kubelet --since "30 minutes ago" - Of het cluster external CA mode gebruikt (check of
/etc/kubernetes/pki/ca.keybestaat) - Of etcd stacked of extern draait
- Met welke kubeadm-versie het cluster oorspronkelijk is gebootstrapt (vaak zichtbaar in de image tag history van
/etc/kubernetes/manifests/kube-apiserver.yaml) - Of
/var/lib/kubelet/pki/kubelet-client-current.pemeen symlink is en waar die naar wijst - Of de werkstation-kubeconfig al ververst is vanuit de vernieuwde
admin.conf
Multi-node HA-uitval (waarbij meer dan één control-plane node geraakt is en quorum onduidelijk is) is het meest waarschijnlijke scenario waarin escaleren ook echt nodig is. Single-node-vernieuwen is mechanisch; HA-vernieuwen waarbij node 1 lukte maar node 2 tijdens de restart zijn etcd-quorum kwijtraakte, dáár wordt het interessant.
Voorkomen dat het opnieuw gebeurt
Het doel: het symptoom uit dit artikel nooit meer zien.
- Draai
kubeadm upgrademinstens elke 6 maanden. Daarmee worden alle certificaten binnen het 180-dagen-window automatisch vernieuwd. Het is ook een veiligere cadans om op een ondersteunde Kubernetes-versie te blijven, want een Kubernetes minor release wordt ongeveer 14 maanden ondersteund. - Monitor certificate expiry actief. Een simpele Prometheus-alert op de metric
apiserver_client_certificate_expiration_seconds, of een CronJob diekubeadm certs check-expirationdraait en mailt als residual<60dis, vangt dit voor het een outage wordt. Alert op 90 dagen, page op 30 dagen. - Kun je naar managed Kubernetes, doe dat dan. De operationele kosten van een eigen kubeadm-cluster runnen omvatten dit artikel. EKS, GKE en AKS roteren control-plane-certificaten voor je. AKS roteert non-CA-certificaten op 80% van de valid time op clusters vanaf maart 2022. GKE roteert etcd-certificaten 6 maanden voor expiry automatisch.
- Voor workload-TLS, gebruik cert-manager. kubeadm's PKI is voor de interne control plane van het cluster; het is niet de juiste tool om certificaten aan je applicaties uit te geven. Zie daarvoor Kubernetes TLS met cert-manager: geautomatiseerd certificaatbeheer.
- Verleng de standaardlevensduur als je operationele cadans lang is. kubeadm 1.31 en later ondersteunen
certificateValidityPeriodinClusterConfiguration(defaults:8760hvoor leaves,87600hvoor CA's). Een leaf-levensduur van 3 jaar is ongebruikelijk maar niet verkeerd als je weet dat je cadans niet anders kan en je accepteert dat de blast radius bij een gecompromitteerd certificaat groter is.
Het doel is niet nul certificaatwerk. Het doel is geen verrassingen. Een vernieuwing van vijf minuten in een gepland upgradevenster verslaat een x509-outage om vier uur 's nachts op elke operationele meetlat.