0/3 nodes are available: node(s) had untolerated taint
Fix "0/3 nodes are available: node(s) had untolerated taint": run kubectl describe node, then add a toleration or remove the taint. Covers control-plane nodes.
0/3 nodes are available: 3 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/3 nodeIncident Overview
KubernetesKubernetes
Fix "0/3 nodes are available: node(s) had untolerated taint": run kubectl describe node, then add a toleration or remove the taint. Covers control-plane nodes.
medium fix·6 min read
By Vishvesh Patel·DevOps Engineer·Updated 21 September 2026
0/3 nodes are available: 3 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.
0/5 nodes are available: 2 node(s) had untolerated taint {node.kubernetes.io/not-ready: }, 3 node(s) had untolerated taint {dedicated: gpu}.Do this first3 steps
Run these in order. Each one tells you what its output means before you change anything.
• Read the exact taint from the scheduler's own message
The message names the taint in braces, for example {node-role.kubernetes.io/control-plane: } or {dedicated: gpu}. That key and value are what a toleration has to match. Read it before changing anything.
• List every taint in the cluster
If every node carries the same taint you have a cluster-wide policy, not a scheduling accident. On a single-node cluster this is almost always the control-plane taint.
• Add a matching toleration, or remove the taint if it should not be there
The trailing hyphen removes the taint. Only do this when the taint is genuinely wrong. If the taint is deliberate, add a toleration to the Pod spec instead of stripping the cluster's protection.
node(s) had untolerated taint means the scheduler did find nodes, and every one of them pushed your Pod away. A taint is a node saying "nothing runs here unless it explicitly says it can tolerate this". Your Pod does not say that.
This is different from Insufficient cpu, which is a capacity problem. Here there may be plenty of room; the nodes are simply refusing.
kubectl describe pod my-pod | grep -A5 Events
Read the taint first
The scheduler tells you exactly which taint blocked it. Do not guess:
The part in braces is a key: value pair. Here the key is node-role.kubernetes.io/control-plane and the value is empty. That pair is what a toleration has to match.
A cluster with mixed taints gives you a breakdown:
Two separate problems in one message. Two nodes are unhealthy and three are reserved.
See all of them at once:
kubectl describe pod my-pod
The single-node case, which is most of them
If you built a cluster with kubeadm and never joined a worker, every node is a control-plane node and carries node-role.kubernetes.io/control-plane:NoSchedule. Nothing you deploy will ever schedule.
This is by design: control-plane nodes are meant to stay clear of workloads. On a one-node lab cluster that design is in your way, so remove it deliberately:
The trailing - is the removal syntax and it is easy to miss. Without it you are adding a taint, not removing one.
Older clusters use node-role.kubernetes.io/master instead, and some carry both. Remove whichever kubectl describe node actually shows.
Do not do this on a real cluster. Add worker nodes instead.
kubectl taint nodes --all node-role.kubernetes.io/control-plane:NoSchedule-
The three effects
NoExecute is the one that causes an incident. Taint a node with it and running Pods are removed, not just kept away. That is exactly what Kubernetes does automatically when a node goes unhealthy, which is how a node problem turns into Pods moving.
Fix 1: Tolerate the taint
When the taint is correct and your Pod genuinely belongs there, add a toleration:
For a taint with no value, such as the control-plane one, use Exists:
A toleration is permission, not preference. It lets the Pod land on a tainted node; it does not make the scheduler prefer one. If you want the Pod to actually go to the GPU nodes rather than merely be allowed to, pair the toleration with a nodeSelector or node affinity:
Without the selector, a tolerating Pod will happily take a normal node and leave the expensive GPU capacity idle. This is the single most common mistake with taints.
spec:
tolerations:
- key: dedicated
operator: Equal
value: gpu
effect: NoScheduleFix 2: Remove the taint
Only when the taint should not be there. Something automated may put it back.
If the taint reappears within a minute or two, something is re-applying it: a node pool configuration, a cloud provider setting, or a DaemonSet-managed agent. Fix it there, because the cluster will keep undoing you.
# Remove a specific taint by key, value and effect kubectl taint nodes work-2 dedicated=gpu:NoSchedule- # Remove every taint with a given key, whatever its value kubectl taint nodes work-2 dedicated-
The taints Kubernetes adds by itself
These are never yours and they mean the node is unhealthy:
Tolerating these is almost always wrong. It tells the scheduler to place work on a node you have been told is broken. Fix the node instead.
unschedulable is the exception in one direction: it is what kubectl cordon sets, so if you cordoned a node during maintenance and forgot, kubectl uncordon <node> is the fix.
Why DaemonSets seem exempt
They are not exempt, they are tolerant. The DaemonSet controller adds tolerations for the standard node-condition taints automatically, because a log shipper or CNI agent needs to run on a node precisely when that node is in trouble. It is a good illustration of when tolerating a health taint is correct: for infrastructure that exists to observe or repair the node.
A checklist
Frequently Asked Questions
What does "node(s) had untolerated taint" actually mean?
The scheduler found candidate nodes, and every one of them carries a taint your Pod has no matching toleration for, so it refused to place the Pod on any of them. It is a permissions problem rather than a capacity problem: there may be plenty of free CPU and memory. The scheduler names the offending taint in braces in the FailedScheduling event, for example {dedicated: gpu}, and that key and value are exactly what a toleration must match.
How do I remove the control-plane taint on a single-node cluster?
kubectl taint nodes --all node-role.kubernetes.io/control-plane:NoSchedule-. The trailing hyphen is the removal syntax and is easy to miss; without it you are adding the taint rather than removing it. Older clusters use node-role.kubernetes.io/master and some carry both, so check kubectl describe node for the exact key. This is appropriate on a lab or single-node cluster where there are no workers, and a bad idea on a real cluster, where the taint exists to keep workloads off the control plane.
What is the difference between NoSchedule and NoExecute?
NoSchedule stops new Pods landing on the node but leaves anything already running alone. NoExecute also evicts the Pods that are already there. That difference matters a great deal in production: applying a NoExecute taint to a busy node moves live workloads immediately. It is also how Kubernetes reacts to node problems automatically, adding node.kubernetes.io/unreachable:NoExecute when a kubelet stops reporting, which is why a node going quiet causes Pods to move.
Why does my Pod still not run on the GPU node after I added a toleration?
Because a toleration is permission, not preference. It allows the Pod onto a tainted node; it does not direct it there. The scheduler is still free to pick any node that fits, and it will often pick an ordinary one, leaving the expensive capacity idle. Pair the toleration with a nodeSelector or node affinity that matches a label on the GPU nodes. Taints keep the wrong workloads off; labels and selectors pull the right ones on. You generally need both.
Should I tolerate node.kubernetes.io/not-ready?
Almost never for an application. That taint means the node is unhealthy, and tolerating it tells the scheduler to place work somewhere Kubernetes has already flagged as broken. Fix the node instead: check kubectl describe node for the condition, and the kubelet logs on the host. The legitimate exception is infrastructure that must run on a sick node in order to observe or repair it, which is why the DaemonSet controller adds tolerations for the node-condition taints to its Pods automatically.
Reference and practice
Learn the underlying concept
Other Kubernetes errors
Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.
Get notified about new tutorials and features.
• Roadmaps
• Interview Prep
• Certifications
• Company
© 2026 DevOpsLesson. All rights reserved.
Need to practice this scenario?
Simulate this error condition and practice recovery commands safely in our in-browser terminal.