Skip to main content
DevOps Insightsguides & answers

Kubernetes · troubleshooting

PersistentVolumeClaim stays Pending

The PersistentVolumeClaim stays Pending because it asks for storage nobody has offered yet. Either no StorageClass is provisioning, the class is waiting for a pod on purpose, or the claim asks for something no volume can satisfy — and the claim's events say which in one line.

First published

New to this? Read what is actually happening first, then come back to the steps.

On this page · 9 sections

Before you start

  • kubectl read access to the claim and its events in the namespace, plus cluster-wide read on storageclasses and persistentvolumes. PersistentVolumes belong to the cluster, not to a namespace, so a namespace-only role cannot list them.
  • Knowing how this cluster gets volumes: which provisioner, such as a CSI driver, each StorageClass uses, or whether an administrator creates PersistentVolumes by hand.
  • Cluster-admin rights, or agreement from whoever owns storage, before you mark a class as default. The change applies to every namespace and is also written onto existing claims that leave storageClassName unset.
  • Before you delete any claim, its STATUS and the reclaim policy of its volume or class. Deleting a bound claim whose volume has the Delete policy also deletes the stored data.
  • Kubernetes v1.28 or later if you rely on a newly created default class being filled in on existing claims (retroactive default StorageClass assignment). Only claims that leave storageClassName unset are updated; one that sets it to "" is not.

Run this first

step 1 of 3
kubectl describe pvc <name> | sed -n '/Events/,$p'

Look for waiting for first consumer (or waiting for pod <name> to be scheduled once a pod uses the claim), storageclass not found, or no persistent volumes available

The events name the reason directly. Each cause here produces a different message, so this usually ends the investigation rather than starting it.

Work out which cause you have

A few questions to narrow the list. Every answer ends in a command that confirms or rules the cause out — this cannot see your cluster, so nothing here is a certainty until you have checked.

Narrow it down

0 answered · nothing is sent anywhere

What do the claim's events say?

kubectl describe pvc <name> | sed -n '/Events/,$p'

Cause space

4 of 4 still possible

  • The class is waiting for a pod, by designCommon
  • The claim named no class and the cluster has no defaultCommon
  • The named StorageClass does not exist hereCommon
  • No volume satisfies the size or access modeOccasional

Nothing ruled out yet. Answer the question above and the branches your answer eliminates will strike through here.

Or diagnose it manually

In this order. The first command usually contains the whole answer.

  1. Step 1

    kubectl describe pvc <name> | sed -n '/Events/,$p'

    The events name the reason directly. Each cause here produces a different message, so this usually ends the investigation rather than starting it.

    Look for waiting for first consumer (or waiting for pod <name> to be scheduled once a pod uses the claim), storageclass not found, or no persistent volumes available

  2. Step 2

    kubectl get storageclass

    Establishes whether a default exists and whether the named class is present at all.

    Look for A class marked (default), and the name the claim asked for

  3. Step 3

    kubectl get pvc <name> -o jsonpath='{.spec.resources.requests.storage}{" "}{.spec.accessModes}{"\n"}'

    What the claim is actually asking for, as opposed to what the manifest intended.

    Look for A size and access mode the backend can offer

Every cause, and how to fix it

Ordered by how often each one turns out to be the answer.

The class is waiting for a pod, by design

Common

volumeBindingMode: WaitForFirstConsumer delays provisioning until a pod using the claim is scheduled, so the volume is created in the same zone as the node that will mount it. Until then the claim is Pending and nothing is wrong. It becomes a real problem when the pod cannot schedule for some other reason, and then the pod's events hold the answer. It also never binds if the pod sets nodeName, because that skips the scheduler step that triggers provisioning.

Confirm

kubectl get sc "$(kubectl get pvc <name> -o jsonpath='{.spec.storageClassName}')" -o jsonpath='{.volumeBindingMode}{"\n"}'

WaitForFirstConsumer. Then check whether any pod actually references this claim yet.

Fix

  • If no pod uses the claim yet, nothing is broken — create the workload and the volume follows.
  • If a pod does reference it and both are Pending, read the pod's events. The volume is waiting on scheduling, and the scheduling is failing for its own reason.
kubectl describe pod <pod> | sed -n '/Events/,$p'

The pod's events, not the claim's, explain a deadlock between the two.

The claim named no class and the cluster has no default

Common

A claim that omits storageClassName is filled by whichever StorageClass carries the default annotation. Whether a cluster has one depends on how it was installed, so a manifest that works on one cluster can hang here on another. The claim is valid and nothing is listening.

Confirm

kubectl get storageclass

A class marked (default). If none is, an unnamed claim has nothing to bind to.

Fix

  • Name a class explicitly in the claim. This is the more portable fix, because it stops the manifest depending on cluster-wide state.
  • Or mark one class as default, if every workload on this cluster should use it.
kubectl patch storageclass <name> -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

Only one class should carry this annotation. With several defaults, Kubernetes uses the most recently created one. Since v1.28 the control plane also writes the new default onto existing claims that leave storageClassName unset (not ones set to ""), so a claim stuck here can be provisioned without being recreated.

The named StorageClass does not exist here

Common

The claim names a class that is absent on this cluster — usually a manifest moved between environments with different storage backends. Class names are not standardised: gp2, standard and local-path each mean something somewhere and nothing everywhere.

Confirm

kubectl describe pvc <name> | grep -i storageclass

A name that does not appear in kubectl get storageclass

Fix

  • Change the claim to a class this cluster actually has.
  • storageClassName cannot be edited after creation. The claim has to be deleted and recreated. That costs nothing if it never bound. If it did bind and its volume's reclaim policy is Delete, the default for dynamically provisioned volumes, deleting the claim deletes the volume and its data, so check the claim's STATUS and the volume's RECLAIM POLICY before deleting.

No volume satisfies the size or access mode

Occasional

On a cluster using statically created PersistentVolumes rather than a provisioner, binding is a matching exercise: capacity must be at least what the claim asks for, and the access mode must be one the volume offers. ReadWriteMany is the usual stumbling block, because many block-storage backends only offer ReadWriteOnce.

Confirm

kubectl get pv

Available volumes with their CAPACITY and ACCESS MODES. Compare against what the claim requests.

Fix

  • Reduce the request, or provision a volume that matches it.
  • If the workload genuinely needs ReadWriteMany, the backend has to support it — a file-based backend rather than a block device. Changing the access mode on the claim alone does not create the capability.

Understanding it properly

Skip this if you are mid-incident — the working part of the page is above. Worth reading afterwards, because understanding the mechanism is what stops the next one.

In plain terms

A PersistentVolumeClaim is a request for storage that a pod can mount. Kubernetes answers it by binding it to a volume, either one an administrator made in advance or one a storage class creates on demand. Pending means the request has no answer yet, and the claim's events usually say why in one line.

Reading the list of storage classes

Output quoted from the Kubernetes task page Change the default StorageClass, on a cluster with two classes that use the in-tree GCE persistent disk provisioner. No cluster was run for this page.

Command

kubectl get storageclass

Output · as shown in Kubernetes — Change the default StorageClass

NAME                 PROVISIONER               AGE
standard (default)   kubernetes.io/gce-pd      1d
gold                 kubernetes.io/gce-pd      1d

How to read it

  • (default) marks the class a claim gets when it sets no storageClassName. If no row carries it, such a claim stays Pending with the event no persistent volumes available for this claim and no storage class is set until a default exists.
  • NAME is the exact value a claim must put in storageClassName. A claim that names a class missing from this list gets a ProvisioningFailed event that reads storageclass.storage.k8s.io "<name>" not found.
  • PROVISIONER says what creates the volumes. With an external provisioner such as a CSI driver, a waiting claim shows an event starting Waiting for a volume to be created either by the external provisioner; if it never moves on, check that the driver is running.
  • This sample comes from an older kubectl. Current versions also print RECLAIMPOLICY and VOLUMEBINDINGMODE, which tell you whether deleting a claim deletes its data and whether the class waits for a pod before it provisions.

Words used on this page

PersistentVolumeClaim (PVC)
A request for storage of a given size and access mode, made in a namespace. A pod mounts the claim, not the volume behind it.
PersistentVolume (PV)
A piece of storage in the cluster, created by an administrator or by a provisioner. It belongs to the whole cluster and binds to exactly one claim.
StorageClass
A named kind of storage with the provisioner that creates it and its settings. A claim picks one with storageClassName; the name is whatever the administrator chose.
Provisioner
The plugin a StorageClass names to create volumes on demand, for example a CSI driver. If it is missing or not running, claims of that class wait.
Default StorageClass
The class whose annotation storageclass.kubernetes.io/is-default-class is "true". A claim that sets no class gets it, and kubectl get storageclass marks it (default).
volumeBindingMode
When a class binds and creates volumes. Immediate, the default, acts as soon as the claim exists. WaitForFirstConsumer waits until a pod that uses the claim is scheduled, so the volume lands where the pod can reach it.
Access mode
How a volume may be mounted: ReadWriteOnce (one node), ReadOnlyMany and ReadWriteMany (many nodes), or ReadWriteOncePod (one pod). A claim binds only to a volume that offers the mode it asks for.
Bound
The claim's status once it is joined to a volume. The binding is one claim to one volume, and it is exclusive.
What is actually happening

A PersistentVolumeClaim is a request, not storage. Something has to answer it: a matching PersistentVolume that already exists, or a provisioner named by a StorageClass that creates one on demand. Pending means the request is sitting there unanswered.

The pod that mounts it stays Pending too. When the claim's class binds immediately, the pod's message is pod has unbound immediate PersistentVolumeClaims, which points at the claim rather than at the pod. Reading the pod instead of the claim is the usual wasted hour.

One Pending state is not a fault at all. A StorageClass with volumeBindingMode: WaitForFirstConsumer deliberately holds the claim until a pod is scheduled, so the volume is created where the pod will run. The claim shows Pending and the events say exactly that. On a cluster where nothing has scheduled yet, this is the system working.

The rest are mismatches: a class name that does not exist here, no default class on a cluster whose manifest omitted one, an access mode the backend does not offer, or a size nothing available satisfies. Each produces a different event, which is why the events are worth reading before anything is changed.

How to tell this is your problem
WhereWhat you see
kubectl get pvcSTATUS Pending, VOLUME empty
kubectl describe pvc <name>An event naming the reason: waiting for first consumer (or waiting for pod <name> to be scheduled once a pod uses the claim), storageclass not found, or no persistent volumes available
kubectl get podsThe mounting pod is Pending, never Running
kubectl describe pod <name>pod has unbound immediate PersistentVolumeClaims, for a class with Immediate binding. With WaitForFirstConsumer you see scheduler reasons such as node(s) didn't find available persistent volumes to bind instead

How to know it is actually fixed

  • kubectl get pvc <name> shows STATUS Bound and a VOLUME name.
  • The pod that mounts it leaves Pending and reaches Running.
  • Where a provisioner was involved, kubectl describe pvc <name> shows a ProvisioningSucceeded event.

Stopping it happening again

  • Name the StorageClass explicitly in claims rather than relying on a cluster default, so the manifest means the same thing in every environment.
  • Keep one default class per cluster, not two.
  • Set the class through chart values where one is offered, since storageClassName cannot be changed once the claim names a class.

Security considerations

  • Changing the class a claim names means deleting and recreating it. If the claim was ever Bound and its volume's reclaim policy is Delete, the default for dynamically provisioned volumes, deleting the claim deletes the PersistentVolume and the storage behind it. Check RECLAIM POLICY in kubectl get pv first.
  • Marking a class as default is a cluster-wide change. Every new claim without a class, in every namespace, gets it, and existing claims that leave storageClassName unset are updated to it, so check what that class costs and where it stores data before you set the annotation.
  • A hand-made PersistentVolume binds to the first claim that matches its class, size and access mode, whichever namespace it comes from. If the volume already holds data, reserve it for one claim with the PV's claimRef so no other claim can bind to it.
  • Access modes are matching rules, not protection. The documentation says even a volume created as ReadOnlyMany is no guarantee that it will be read-only, so do not rely on the mode to keep data safe.
  • A volume kept with the Retain policy still holds the previous claim's data after release. Clean the data before you clear its claimRef to make it Available for a new claim.

Sources

Behaviour described here is drawn from official documentation. Where a figure could not be confirmed on an official page it is attributed in the text rather than stated as canonical.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Something wrong or out of date? Report it