Skip to main content

Chris Engelbert Chris Engelbert

Pod Has Unbound Immediate PersistentVolumeClaims: Causes and Fixes

Aug 13, 2026  |  12 min read

Last edited: Sep 2, 2026

Pod Has Unbound Immediate PersistentVolumeClaims: Causes and Fixes

A pod sits in Pending. You run kubectl describe pod and the events end with a line from the scheduler:

Warning FailedScheduling 0/6 nodes are available: pod has unbound immediate PersistentVolumeClaims.

It reads like a capacity problem, so the first instinct is usually to look at nodes. That is almost always the wrong place to look. The scheduler is not telling you it ran out of room. It is telling you it refuses to even start evaluating nodes, because a volume the pod depends on does not exist yet, and the scheduler will not place a pod whose storage might never arrive.

This post walks the five things that actually cause it, in the order that isolates the problem fastest, then covers the part most guides skip: the standard fix trades a scheduling error for a mobility ceiling, and whether that trade matters depends entirely on what your storage layer looks like underneath.

What the error message actually means

Two separate mechanisms have to line up before a pod with a PersistentVolumeClaim can run: the claim has to bind to a volume, and the scheduler has to pick a node.

The word immediate in the message is not a description of urgency. It is the name of a StorageClass setting: volumeBindingMode: Immediate. With that mode, binding happens the moment the PVC is created, with no reference to any pod. The provisioner picks where the volume lives, the claim goes to Bound, and only later does a pod come along asking to use it.

The scheduler’s volume-binding plugin checks, for every pod it considers, whether all Immediate-mode claims are already bound. If any is still Pending, the plugin rejects the pod for every node at once and emits exactly the message above. The node count in the message (0/6 nodes are available) is misleading: no node was ever actually assessed.

So the error is a symptom with one meaning, and it always points at the claim, never at the nodes:

Terminal window
# Confirm which claim is the problem
kubectl get pvc -n <namespace>
# The events on the claim carry the real reason
kubectl describe pvc <claim-name> -n <namespace>

If the claim shows Pending, the describe output’s event list is where the actual cause lives. Everything below is a way of reading that output.

Five causes, in the order worth checking

These are ordered by how often they turn out to be the answer and how cheap they are to rule out, not by severity.

#CauseFastest checkWhat you see
1No StorageClass resolveskubectl get scNo class marked (default), and the PVC has no storageClassName
2Provisioner is not running or is failingkubectl describe pvc eventsProvisioningFailed, or no provisioner event at all
3Topology conflict under Immediatekubectl get pv -o wideVolume bound in one zone, pod constrained to another
4Access mode not supportedkubectl describe pvcClaim requests ReadWriteMany, driver offers ReadWriteOnce
5Static PV mismatch or quotakubectl get pv, kubectl describe quotaNo PV satisfies size, class, or selector; or storage quota exhausted

Table 1: Root causes of an unbound immediate PVC, ordered by how quickly each can be ruled out.

1. No StorageClass resolves. A PVC created without storageClassName relies on a cluster default. If no class carries the storageclass.kubernetes.io/is-default-class: "true" annotation, nothing ever picks the claim up, and it will sit Pending indefinitely with no error event, which is what makes this one confusing. Two defaults is equally broken: Kubernetes refuses to choose. Set exactly one, or name the class explicitly in the claim.

2. The provisioner is not running or is failing. The CSI driver controller has to be up, authorized against the backend, and able to satisfy the request. A crash-looping controller pod, expired backend credentials, or a full storage pool all surface here as a ProvisioningFailed event on the claim. Read the controller logs, not the pod’s:

Terminal window
kubectl -n <csi-namespace> logs deploy/<csi-controller> -c csi-provisioner --tail=100

3. A topology conflict under Immediate binding. This is the interesting one, and the reason the error exists at all. Under Immediate, the volume gets placed before anyone knows where the pod will run. If the provisioner put it in availability zone A, and the pod carries a nodeSelector, an affinity rule, or a second volume that constrains it to zone B, no node can satisfy both. The claim is bound, the pod is unschedulable, and the two facts never reconcile on their own. Deleting and recreating the claim is a coin flip, not a fix.

4. The access mode is not supported. A claim asking for ReadWriteMany against a driver that only offers ReadWriteOnce has no candidate volume, so it stays Pending. This bites hardest on workloads moved from a shared-filesystem world, where multi-writer access was simply assumed.

5. A static PV mismatch, or a quota ceiling. When you pre-provision volumes by hand, binding requires the PV to match the claim on capacity, access modes, storageClassName, and any label selector, with a PV that is genuinely Available rather than Released from an earlier claim. Separately, a ResourceQuota capping requests.storage or the persistentvolumeclaims count will block provisioning with a quota event on the claim.

Chasing unbound PVCs across zones every time a node goes down? If binding mode and topology have become a recurring scheduling constraint rather than a one-off misconfiguration, the storage layer underneath is worth a conversation. Talk to a storage architect

Immediate vs WaitForFirstConsumer, and what the workaround costs

The advice you will find everywhere is to switch the StorageClass to volumeBindingMode: WaitForFirstConsumer. That advice is correct, and it does fix cause 3 properly. It is worth understanding why, because the mechanism explains the catch.

With WaitForFirstConsumer, the claim deliberately stays unbound until a pod references it. The scheduler picks the node first, using all the pod’s real constraints, and then passes that placement decision to the provisioner as a topology hint. The volume is created where the pod already is. The ordering that caused the conflict is reversed, so the conflict cannot occur.

What that does not change is where the volume physically lives. If the storage layer is node-local, meaning the volume is carved out of disks attached to one specific machine, then the resulting PersistentVolume is stamped with a nodeAffinity naming that machine. From then on the pod can only ever be scheduled there. That is fine on day one. It is not fine when the node needs draining for maintenance, or fails outright: the pod becomes unschedulable again, and this time the fix is a data rebuild rather than a config change.

So WaitForFirstConsumer on node-local storage does not remove the coupling between volumes and nodes. It moves the moment you feel it, from first deploy to first node loss. This is the same class of problem as treating ephemeral storage as if it were durable: the architecture looks correct until the cluster changes shape.

Storage layerImmediate bindingWaitForFirstConsumerAfter a node is lost
Node-local disksTopology conflicts are likelyConflicts resolved, pod pinned to one nodePod unschedulable, data rebuild required
Per-zone cloud block storageConflicts when zones disagreeConflicts resolved, pod pinned to one zoneReschedulable inside the zone only
Shared pool over a storage fabricSafe, binding implies no placementAlso safe, no pinning introducedReattach on any node in the pool

Table 2: How binding mode interacts with the storage layer underneath, and what each combination means when a node disappears.

Why the problem disappears with a shared storage pool

The root of every topology variant of this error is the same assumption: that a volume belongs to a place. Break that assumption and the whole class of failure stops applying.

Simplyblock presents volumes from a single pool over NVMe/TCP or NVMe/RoCE, which means any node on the fabric can attach any volume. Three consequences follow directly, and each one maps to a cause in the table above.

Diagram showing three common causes of unbound PVCs on the left, a shared NVMe/TCP pool in the centre, and the resulting scheduling behaviour on the right
Figure 1: With volumes served from one shared pool, binding a claim no longer commits the pod to a node or a zone.

Binding a claim stops implying a placement. Under Immediate mode against a shared pool, the claim binds straight away, as designed, but the resulting volume carries no nodeAffinity restricting where it can be used. The scheduler evaluates the pod against every node on its real merits. Cause 3 has nothing to act on.

Capacity comes from the pool, not from one machine’s free space. A provisioning failure caused by the specific node the scheduler happened to choose having no room left is not a scenario that arises, because provisioning is not scoped to a node in the first place. Compute and storage scale independently: adding storage capacity does not mean adding compute nodes, and vice versa.

One class serves both access modes. Because the volume is reachable from multiple nodes over the fabric, ReadWriteMany block is available from the same StorageClass that serves ReadWriteOnce, which removes cause 4 as a design constraint rather than as a per-claim workaround. This is what makes live migration work for VM workloads on KubeVirt and Red Hat® OpenShift® Virtualization, where the destination node needs a write-capable attachment before the source releases one.

The operational payoff shows up on the day a node dies. Rescheduling a stateful pod becomes a volume reattachment measured in seconds, not a replica rebuild measured in the time it takes to stream the dataset across the network. Licensing follows the same logic: simplyblock is priced like the platform it runs on, by the CPU capacity of your worker nodes counted the way the subscription counts them, and data volume does not change the price.

What to fix now, and what to fix once

Separate the two timescales, because they call for different actions.

Right now, to get the pod running: read the events on the claim, not on the pod. Confirm exactly one default StorageClass exists, or name one in the claim. Check the CSI controller is healthy and its credentials are valid. If the claim is Bound but the pod still will not schedule, compare the volume’s zone against the pod’s constraints, since that combination is cause 3 and nothing but a rebind or a constraint change will clear it. If a static PV is involved, diff its capacity, access modes, class, and selector against the claim field by field.

Once, so it stops recurring: set WaitForFirstConsumer as the default on any StorageClass backed by node-local or zonal storage, since it is strictly better than Immediate there. Then decide, deliberately, whether your stateful workloads should be pinned to nodes at all. If they should not, the binding-mode question becomes a detail rather than a recurring operational tax, because the volume is no longer tied to a location the scheduler has to reason about.

Questions and Answers

What does “pod has unbound immediate PersistentVolumeClaims” mean?

It means the pod references at least one PersistentVolumeClaim that is still Pending, on a StorageClass using volumeBindingMode: Immediate. The scheduler’s volume-binding plugin rejects the pod before evaluating any node, so the node count in the message is misleading. The cause is always on the claim, and kubectl describe pvc <name> is where the real event is.

How do I fix an unbound immediate PersistentVolumeClaim?

Work through the claim, not the nodes. Confirm exactly one default StorageClass exists or that the claim names one; check the CSI controller is running and authorized; compare the bound volume’s zone against the pod’s node constraints; verify the requested access mode is one the driver supports; and if the PV was created by hand, check capacity, access modes, class, and selector all match. For a lasting fix on node-local or zonal storage, switch the StorageClass to WaitForFirstConsumer.

Should I always use WaitForFirstConsumer instead of Immediate?

On node-local or per-zone storage, yes: it lets the scheduler choose the node first and provisions the volume where the pod already is, so topology conflicts cannot happen. But it does not make the volume portable. On node-local storage the resulting PV is pinned to one machine by nodeAffinity, so you trade a first-deploy error for an unschedulable pod after node loss. Against a shared pool reachable from every node, neither mode introduces pinning, so the choice stops mattering.

Does simplyblock avoid this error?

Simplyblock removes the topology variant of it, which is the one that recurs. Volumes are served from one pool over NVMe/TCP or NVMe/RoCE and carry no node affinity, so binding a claim never commits the pod to a node or a zone, capacity is drawn from the pool rather than one machine’s free space, and ReadWriteMany block comes from the same StorageClass as ReadWriteOnce. The configuration causes remain possible, since a missing default StorageClass or a down CSI controller will stall any driver, but they are one-time setup faults rather than an ongoing constraint on scheduling.

Why is the pod still Pending after the PVC shows Bound?

That combination is the topology conflict, cause 3 above. The claim bound successfully, but to a volume in a zone or on a node the pod is not allowed to run on, because Immediate mode placed the volume before the scheduler saw the pod’s constraints. Nothing resolves this on its own. Either relax the pod’s constraints, or delete the claim and recreate it under a WaitForFirstConsumer class so placement drives provisioning rather than the reverse.

You may also like:

Ephemeral Storage in Kubernetes: Why It Silently Breaks Stateful Workloads
Ephemeral Storage in Kubernetes: Why It Silently Breaks Stateful Workloads

emptyDir and other ephemeral storage look fast and simple, until a pod eviction or node failure erases the data. Here is what actually happens, and how NVMe/TCP persistent volumes give you the same latency without the risk.

NVMe/TCP vs NVMe/RoCE for Kubernetes Storage: Choosing the Right Fabric
NVMe/TCP vs NVMe/RoCE for Kubernetes Storage: Choosing the Right Fabric

NVMe over Fabrics gives Kubernetes clusters low-latency block storage over the network. The transport you pick, TCP or RoCE, determines your latency floor, infrastructure cost, and operational complexity. Here is how to choose.

How to Benchmark Block Storage Performance: fio and Network Tuning Best Practices
How to Benchmark Block Storage Performance: fio and Network Tuning Best Practices

A practical methodology for benchmarking distributed block storage: how to configure fio, why warm-up matters, how to read mixed read/write results, and the network tuning that lets NVMe/TCP reach line rate.